@casekit/orm 0.0.1-alpha.14 → 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,45 @@
1
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
2
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
3
+ import { WhereClause } from "../../clauses/WhereClause";
4
+
5
+ export type CountBuilder = {
6
+ tableIndex: number;
7
+
8
+ table: {
9
+ table: string;
10
+ model: string;
11
+ schema: string;
12
+ alias: string;
13
+ joins: Join[];
14
+ conditions?: WhereClause<
15
+ LooseModelDefinitions,
16
+ ModelName<LooseModelDefinitions>
17
+ >;
18
+ where?: WhereClause<
19
+ LooseModelDefinitions,
20
+ ModelName<LooseModelDefinitions>
21
+ >;
22
+ };
23
+ };
24
+
25
+ export type Join = {
26
+ from: {
27
+ schema: string;
28
+ table: string;
29
+ alias: string;
30
+ model: string;
31
+ columns: string[];
32
+ };
33
+ to: {
34
+ schema: string;
35
+ table: string;
36
+ alias: string;
37
+ model: string;
38
+ columns: string[];
39
+ };
40
+ where?: WhereClause<
41
+ LooseModelDefinitions,
42
+ ModelName<LooseModelDefinitions>
43
+ >;
44
+ type?: "inner" | "left";
45
+ };
@@ -0,0 +1,27 @@
1
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
2
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
3
+ import { LooseRelationsDefinitions } from "../../../schema/types/loose/LooseRelationsDefinitions";
4
+ import { WhereClause } from "../../clauses/WhereClause";
5
+
6
+ export type IncludeHasOneRelationsClause<
7
+ Models extends LooseModelDefinitions,
8
+ Relations extends LooseRelationsDefinitions<Models>,
9
+ M extends ModelName<Models>,
10
+ > = {
11
+ [R in Extract<keyof Relations[M], string>]?: Relations[M][R] extends {
12
+ model: ModelName<Models>;
13
+ }
14
+ ? Relations[M][R] extends { type: "N:1" }
15
+ ? CountParams<Models, Relations, Relations[M][R]["model"]>
16
+ : never
17
+ : never;
18
+ };
19
+
20
+ export type CountParams<
21
+ Models extends LooseModelDefinitions,
22
+ Relations extends LooseRelationsDefinitions<Models>,
23
+ M extends ModelName<Models>,
24
+ > = {
25
+ where?: WhereClause<Models, M>;
26
+ include?: IncludeHasOneRelationsClause<Models, Relations, M>;
27
+ };
@@ -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 { buildCount } from "./count/buildCount";
6
+ import { countToSql } from "./count/countToSql";
7
+ import { BaseCountParams } from "./count/types/BaseCountParams";
8
+
9
+ export const count = async (
10
+ conn: Connection,
11
+ config: BaseConfiguration,
12
+ m: string,
13
+ query: BaseCountParams,
14
+ ) => {
15
+ const builder = buildCount(config, m, query);
16
+ const statement = countToSql(config, builder);
17
+ logger.info({
18
+ message: "Executing query",
19
+ sql: statement.text,
20
+ values: statement.values,
21
+ });
22
+
23
+ if (process.env.ORM_VERBOSE_LOGGING) {
24
+ console.log(statement.text);
25
+ console.log(statement.values);
26
+ }
27
+
28
+ const result = await conn
29
+ .query(statement)
30
+ .then((result) => result.rows[0].count);
31
+
32
+ return result;
33
+ };
@@ -0,0 +1,67 @@
1
+ import { uniq } from "lodash-es";
2
+ import { BaseConfiguration } from "src/schema/types/base/BaseConfiguration";
3
+
4
+ import { OrmError } from "../../errors";
5
+ import { tableAlias } from "../util/tableAlias";
6
+ import { BaseCreateManyParams } from "./types/BaseCreateManyParams";
7
+
8
+ export type CreateBuilder = {
9
+ tableIndex: number;
10
+ table: { table: string; schema: string };
11
+ params: { name: string; path: string; values: unknown[] }[];
12
+ onConflict?: { do: "nothing" };
13
+ returning: { name: string; path: string; alias: string }[];
14
+ };
15
+
16
+ export const buildCreate = (
17
+ config: BaseConfiguration,
18
+ m: string,
19
+ params: BaseCreateManyParams,
20
+ _tableIndex = 0,
21
+ ): CreateBuilder => {
22
+ const builder: CreateBuilder = {
23
+ tableIndex: _tableIndex,
24
+ table: {
25
+ table: config.models[m].table,
26
+ schema: config.models[m].schema,
27
+ },
28
+ params: [],
29
+ returning: [],
30
+ };
31
+
32
+ const table = tableAlias(builder.tableIndex++);
33
+ let colIndex = 0;
34
+
35
+ const model = config.models[m];
36
+ const values = params.values.map((v) =>
37
+ config.middleware.create?.values
38
+ ? config.middleware.create.values(v, { config, model: m })
39
+ : v,
40
+ );
41
+
42
+ if (values.length === 0)
43
+ throw new OrmError("No data provided for create operation", {
44
+ data: { m, params },
45
+ });
46
+
47
+ const keys = uniq(values.flatMap((v) => Object.keys(v)));
48
+ for (const k of keys) {
49
+ builder.params.push({
50
+ name: model.columns[k]["name"],
51
+ path: k,
52
+ values: values.map((v) => v[k]),
53
+ });
54
+ }
55
+
56
+ for (const f of params.returning ?? []) {
57
+ builder.returning.push({
58
+ name: model.columns[f]["name"],
59
+ path: f,
60
+ alias: `${table}_${colIndex++}`,
61
+ });
62
+ }
63
+
64
+ builder.onConflict = params.onConflict;
65
+
66
+ return builder;
67
+ };
@@ -0,0 +1,24 @@
1
+ import { BaseConfiguration } from "src/schema/types/base/BaseConfiguration";
2
+ import { ZodSchema, z } from "zod";
3
+
4
+ import { BaseCreateManyParams } from "./types/BaseCreateManyParams";
5
+ import { BaseCreateOneParams } from "./types/BaseCreateOneParams";
6
+
7
+ export const createResultSchema = (
8
+ config: BaseConfiguration,
9
+ m: string,
10
+ params: BaseCreateManyParams | BaseCreateOneParams,
11
+ ) => {
12
+ if (!params.returning) return z.number();
13
+
14
+ const obj: Record<string, ZodSchema<unknown>> = {};
15
+
16
+ params.returning?.forEach((s) => {
17
+ const col = config.models[m].columns[s];
18
+ obj[s] = col.nullable
19
+ ? col.zodSchema.nullish().transform((x) => x ?? null)
20
+ : col.zodSchema;
21
+ });
22
+
23
+ return z.object(obj);
24
+ };
@@ -0,0 +1,44 @@
1
+ import pgfmt from "pg-format";
2
+
3
+ import { SQLStatement, sql } from "../../sql";
4
+ import { CreateBuilder } from "./buildCreate";
5
+
6
+ export const createToSql = (builder: CreateBuilder): SQLStatement => {
7
+ const frag = new SQLStatement();
8
+
9
+ const { table, params, returning } = builder;
10
+
11
+ if (params.length === 0) {
12
+ frag.push(
13
+ pgfmt(
14
+ "INSERT INTO %I.%I DEFAULT VALUES",
15
+ table.schema,
16
+ table.table,
17
+ ),
18
+ );
19
+ } else {
20
+ frag.push(pgfmt("INSERT INTO %I.%I (\n", table.schema, table.table));
21
+ frag.push(params.map((p) => pgfmt(" %I", p.name)).join(",\n"));
22
+ frag.push(") VALUES ");
23
+ const values = params[0].values.map((_, index) => {
24
+ return sql`\n(${sql.splat(params.map((p) => p.values[index]))})`;
25
+ });
26
+ frag.push(sql.splat(values));
27
+ }
28
+
29
+ if (builder.onConflict?.do === "nothing") {
30
+ frag.push("\nON CONFLICT DO NOTHING");
31
+ }
32
+
33
+ if (returning.length > 0) {
34
+ frag.push("\nRETURNING ");
35
+ frag.push(
36
+ returning
37
+ .map((r) => pgfmt(` %I as %I`, r.name, r.alias))
38
+ .join(",\n"),
39
+ );
40
+ }
41
+ frag.push(";\n");
42
+
43
+ return frag;
44
+ };
@@ -0,0 +1,28 @@
1
+ import { describe, expect, test } from "vitest";
2
+
3
+ import { db } from "../../../test/db";
4
+
5
+ describe("createMany", () => {
6
+ test("it can handle varied combinations of optional keys in its values", async () => {
7
+ await db.transact(
8
+ async (db) => {
9
+ const russell = await db.createOne("user", {
10
+ values: { username: `Russell` },
11
+ returning: ["id", "username"],
12
+ });
13
+ const others = await db.createMany("user", {
14
+ values: [
15
+ { username: "Fairooz" },
16
+ { username: "Dan", invitedById: russell.id },
17
+ ],
18
+ returning: ["username", "invitedById"],
19
+ });
20
+ expect(others).toEqual([
21
+ { username: "Fairooz", invitedById: null },
22
+ { username: "Dan", invitedById: russell.id },
23
+ ]);
24
+ },
25
+ { rollback: true },
26
+ );
27
+ });
28
+ });
@@ -0,0 +1,116 @@
1
+ import pg from "pg";
2
+ import * as uuid from "uuid";
3
+ import { assertType, describe, expectTypeOf, test } from "vitest";
4
+ import { z } from "zod";
5
+
6
+ import { ModelDefinition, orm } from "../../..";
7
+ import { db } from "../../../test/db";
8
+
9
+ describe("createOne", () => {
10
+ test("only models that exist can be created", () => {
11
+ assertType(
12
+ db.createOne(
13
+ // @ts-expect-error model does not exist
14
+ "wrong",
15
+ {},
16
+ ),
17
+ );
18
+ });
19
+
20
+ test("a data param must be included in the query", () => {
21
+ assertType(
22
+ db.createOne(
23
+ "post",
24
+ // @ts-expect-error data param is missing
25
+ {},
26
+ ),
27
+ );
28
+ });
29
+
30
+ test("all required fields must be provided", () => {
31
+ assertType(
32
+ db.createOne("post", {
33
+ // @ts-expect-error required fields not provided
34
+ values: { title: "hello" },
35
+ }),
36
+ );
37
+ });
38
+
39
+ test("only existing fields can be included in the returning clause", () => {
40
+ assertType(
41
+ db.createOne("post", {
42
+ values: {
43
+ title: "hello",
44
+ content: "it me",
45
+ authorId: uuid.v4(),
46
+ },
47
+ returning: [
48
+ "id",
49
+ // @ts-expect-error non-existing fields can't be returned
50
+ "x",
51
+ ],
52
+ }),
53
+ );
54
+ });
55
+
56
+ test("without a returning clause, the return type is the number of rows created", async () => {
57
+ expectTypeOf(
58
+ await db.createOne("post", {
59
+ values: {
60
+ title: "hello",
61
+ content: "it me",
62
+ authorId: uuid.v4(),
63
+ },
64
+ }),
65
+ ).toMatchTypeOf<number>();
66
+ });
67
+
68
+ test("with a returning clause, the return type is an object containing the specified fields", async () => {
69
+ expectTypeOf(
70
+ await db.createOne("post", {
71
+ values: {
72
+ title: "hello",
73
+ content: "it me",
74
+ authorId: uuid.v4(),
75
+ },
76
+ returning: ["id", "title"],
77
+ }),
78
+ ).toMatchTypeOf<{ id: string; title: string }>();
79
+ });
80
+
81
+ test("non-selected fields are not included in the result type", async () => {
82
+ expectTypeOf(
83
+ await db.createOne("post", {
84
+ values: {
85
+ title: "hello",
86
+ content: "it me",
87
+ authorId: uuid.v4(),
88
+ },
89
+ returning: ["id", "title"],
90
+ }),
91
+ ).not.toMatchTypeOf<{ id: string; title: string; content: string }>();
92
+ });
93
+
94
+ test("when there are no required params, typechecking still works", () => {
95
+ const foo = {
96
+ columns: { id: { type: "serial", zodSchema: z.coerce.number() } },
97
+ } as const satisfies ModelDefinition;
98
+ const db = orm({ models: { foo }, pool: new pg.Pool() });
99
+ assertType(db.createOne("foo", { values: { id: 3 } }));
100
+ });
101
+
102
+ test("when all required fields are provided, excess property checking still works", async () => {
103
+ assertType(
104
+ await db.createOne("post", {
105
+ values: {
106
+ title: "hello",
107
+ content: "it me",
108
+ authorId: uuid.v4(),
109
+ // @ts-expect-error non-existing properties can't be included
110
+ wrong: 2,
111
+ },
112
+ returning: ["id", "title"],
113
+ }),
114
+ );
115
+ });
116
+ });
@@ -0,0 +1,197 @@
1
+ import pg from "pg";
2
+ import * as uuid from "uuid";
3
+ import { describe, expect, test } from "vitest";
4
+ import { z } from "zod";
5
+
6
+ import { ModelDefinition, orm } from "../../..";
7
+ import { createTableSql } from "../../../migrate/sql/createTableSql";
8
+ import { sql } from "../../../sql";
9
+ import { db } from "../../../test/db";
10
+
11
+ describe("createOne", () => {
12
+ test("it inserts records into the database", async () => {
13
+ await db.transact(
14
+ async (db) => {
15
+ const userId = uuid.v4();
16
+ const tenantId = uuid.v4();
17
+ const postId = uuid.v4();
18
+ await db.createOne("tenant", {
19
+ values: {
20
+ id: tenantId,
21
+ name: "popovapark",
22
+ },
23
+ });
24
+ await db.createOne("user", {
25
+ values: {
26
+ id: userId,
27
+ username: "russell",
28
+ },
29
+ });
30
+ const result = await db.createOne("post", {
31
+ values: {
32
+ id: postId,
33
+ authorId: userId,
34
+ tenantId: tenantId,
35
+ title: "hello it me",
36
+ content: "i'm writing a post",
37
+ },
38
+ });
39
+
40
+ expect(result).toEqual(1);
41
+
42
+ const rows = await db.findMany("post", {
43
+ select: ["id", "title"],
44
+ });
45
+
46
+ expect(rows).toHaveLength(1);
47
+ expect(rows[0]).toEqual(
48
+ expect.objectContaining({
49
+ id: postId,
50
+ title: "hello it me",
51
+ }),
52
+ );
53
+ },
54
+ { rollback: true },
55
+ );
56
+ });
57
+
58
+ test("columns of type serial do not need to be specified", async () => {
59
+ const baz = {
60
+ columns: {
61
+ id: { type: "serial", zodSchema: z.coerce.number() },
62
+ big: { type: "bigserial", zodSchema: z.coerce.number() },
63
+ small: { type: "smallserial", zodSchema: z.coerce.number() },
64
+ },
65
+ } as const satisfies ModelDefinition;
66
+ await orm({
67
+ schema: "casekit",
68
+ models: { baz },
69
+ relations: { baz: {} },
70
+ pool: new pg.Pool(),
71
+ }).transact(
72
+ async (db) => {
73
+ await db.connection.query(createTableSql(db.models.baz));
74
+
75
+ await db.createOne("baz", {
76
+ values: {},
77
+ });
78
+
79
+ const rows = await db.findMany("baz", {
80
+ select: ["id", "big", "small"],
81
+ });
82
+
83
+ expect(rows).toHaveLength(1);
84
+ expect(rows[0].id).toBeTypeOf("number");
85
+ expect(rows[0].big).toBeTypeOf("number");
86
+ expect(rows[0].small).toBeTypeOf("number");
87
+ },
88
+ { rollback: true },
89
+ );
90
+ });
91
+
92
+ test("columns with default values do not need to be specified", async () => {
93
+ const baz = {
94
+ columns: {
95
+ id: {
96
+ type: "uuid",
97
+ zodSchema: z.string().uuid(),
98
+ default: sql`uuid_generate_v4()`,
99
+ },
100
+ name: { type: "text", zodSchema: z.string() },
101
+ },
102
+ } as const satisfies ModelDefinition;
103
+ await orm({
104
+ schema: "casekit",
105
+ models: { baz },
106
+ relations: { baz: {} },
107
+ pool: new pg.Pool(),
108
+ }).transact(
109
+ async (db) => {
110
+ await db.connection.query(createTableSql(db.models.baz));
111
+
112
+ await db.createOne("baz", {
113
+ values: { name: "hello" },
114
+ });
115
+
116
+ const rows = await db.findMany("baz", {
117
+ select: ["id", "name"],
118
+ });
119
+
120
+ expect(rows).toHaveLength(1);
121
+ expect(rows[0].id).toBeTypeOf("string");
122
+ expect(rows[0].name).toEqual("hello");
123
+ },
124
+ { rollback: true },
125
+ );
126
+ });
127
+ test("columns that are provided by middleware do not need to be specified", async () => {
128
+ const baz = {
129
+ columns: {
130
+ id: {
131
+ type: "uuid",
132
+ zodSchema: z.string().uuid(),
133
+ provided: true,
134
+ },
135
+ name: { type: "text", zodSchema: z.string() },
136
+ },
137
+ } as const satisfies ModelDefinition;
138
+
139
+ await orm({
140
+ schema: "casekit",
141
+ models: { baz },
142
+ relations: { baz: {} },
143
+ pool: new pg.Pool(),
144
+ middleware: [
145
+ {
146
+ create: {
147
+ values: (values) => ({ id: uuid.v4(), ...values }),
148
+ },
149
+ },
150
+ ],
151
+ }).transact(
152
+ async (db) => {
153
+ await db.connection.query(createTableSql(db.models.baz));
154
+
155
+ await db.createOne("baz", {
156
+ values: { name: "hello" },
157
+ });
158
+
159
+ const rows = await db.findMany("baz", {
160
+ select: ["id", "name"],
161
+ });
162
+
163
+ expect(rows).toHaveLength(1);
164
+ expect(rows[0].id).toBeTypeOf("string");
165
+ expect(rows[0].name).toEqual("hello");
166
+ },
167
+ { rollback: true },
168
+ );
169
+ });
170
+
171
+ test("conflicts can be ignored", async () => {
172
+ await db.transact(
173
+ async (db) => {
174
+ await db.createOne("user", { values: { username: "russell" } });
175
+
176
+ // creating a duplicate user should throw an error
177
+ await expect(
178
+ db.transact(async (db) => {
179
+ return db.createOne("user", {
180
+ values: { username: "russell" },
181
+ });
182
+ }),
183
+ ).rejects.toThrow(
184
+ 'duplicate key value violates unique constraint "user_username_idx"',
185
+ );
186
+
187
+ // but if we ignore the conflict, it should work
188
+ const result = await db.createOne("user", {
189
+ values: { username: "russell" },
190
+ onConflict: { do: "nothing" },
191
+ });
192
+ expect(result).toEqual(0);
193
+ },
194
+ { rollback: true },
195
+ );
196
+ });
197
+ });
@@ -0,0 +1,7 @@
1
+ import { NonEmptyArray } from "../../../types/util/NonEmptyArray";
2
+
3
+ export type BaseCreateManyParams = {
4
+ values: Record<string, unknown | null>[];
5
+ onConflict?: { do: "nothing" };
6
+ returning?: NonEmptyArray<string>;
7
+ };
@@ -0,0 +1,7 @@
1
+ import { NonEmptyArray } from "../../../types/util/NonEmptyArray";
2
+
3
+ export type BaseCreateOneParams = {
4
+ values: Record<string, unknown | null>;
5
+ onConflict?: { do: "nothing" };
6
+ returning?: NonEmptyArray<string>;
7
+ };
@@ -0,0 +1,15 @@
1
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
2
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
3
+ import { ReturningClause } from "../../clauses/ReturningClause";
4
+ import { CreateValues } from "./CreateOneParams";
5
+
6
+ export type CreateManyParams<
7
+ Models extends LooseModelDefinitions,
8
+ M extends ModelName<Models>,
9
+ > = {
10
+ values: CreateValues<Models, M>[];
11
+ returning?: ReturningClause<Models[M]>;
12
+ onConflict?: {
13
+ do: "nothing";
14
+ };
15
+ };
@@ -0,0 +1,17 @@
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 { CreateManyParams } from "./CreateManyParams";
6
+ import { CreateOneParams } from "./CreateOneParams";
7
+
8
+ export type CreateManyResult<
9
+ Models extends LooseModelDefinitions,
10
+ M extends ModelName<Models>,
11
+ P extends CreateOneParams<Models, M> | CreateManyParams<Models, M>,
12
+ > =
13
+ P["returning"] extends ReturningClause<Models[M]>
14
+ ? Readonly<{
15
+ [C in P["returning"][number]]: ColumnType<Models, M, C>;
16
+ }>[]
17
+ : number;
@@ -0,0 +1,22 @@
1
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
2
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
3
+ import { Simplify } from "../../../types/util/Simplify";
4
+ import { ReturningClause } from "../../clauses/ReturningClause";
5
+ import { OptionalParams } from "../../clauses/helpers/OptionalParams";
6
+ import { RequiredParams } from "../../clauses/helpers/RequiredParams";
7
+
8
+ export type CreateValues<
9
+ Models extends LooseModelDefinitions,
10
+ M extends ModelName<Models>,
11
+ > = Simplify<RequiredParams<Models, M> & OptionalParams<Models, M>>;
12
+
13
+ export type CreateOneParams<
14
+ Models extends LooseModelDefinitions,
15
+ M extends ModelName<Models>,
16
+ > = {
17
+ values: CreateValues<Models, M>;
18
+ returning?: ReturningClause<Models[M]>;
19
+ onConflict?: {
20
+ do: "nothing";
21
+ };
22
+ };
@@ -0,0 +1,17 @@
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 { SelectClause } from "../../clauses/SelectClause";
5
+ import { CreateManyParams } from "./CreateManyParams";
6
+ import { CreateOneParams } from "./CreateOneParams";
7
+
8
+ export type CreateOneResult<
9
+ Models extends LooseModelDefinitions,
10
+ M extends ModelName<Models>,
11
+ P extends CreateOneParams<Models, M> | CreateManyParams<Models, M>,
12
+ > =
13
+ P["returning"] extends SelectClause<Models[M]>
14
+ ? Readonly<{
15
+ [C in P["returning"][number]]: ColumnType<Models, M, C>;
16
+ }>
17
+ : number;