@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.
- package/build/builders/buildCount.d.ts +23 -0
- package/build/builders/buildCount.js +63 -0
- package/build/builders/buildCount.test.d.ts +1 -0
- package/build/builders/buildCount.test.js +144 -0
- package/build/builders/buildCreate.d.ts +5 -0
- package/build/builders/buildCreate.js +28 -0
- package/build/builders/buildCreate.test.d.ts +1 -0
- package/build/builders/buildCreate.test.js +47 -0
- package/build/builders/buildDelete.d.ts +5 -0
- package/build/builders/buildDelete.js +28 -0
- package/build/builders/buildDelete.test.d.ts +1 -0
- package/build/builders/buildDelete.test.js +108 -0
- package/build/builders/buildFind.d.ts +8 -0
- package/build/builders/buildFind.js +185 -0
- package/build/builders/buildFind.test.d.ts +1 -0
- package/build/builders/buildFind.test.js +263 -0
- package/build/builders/buildUpdate.d.ts +5 -0
- package/build/builders/buildUpdate.js +34 -0
- package/build/builders/buildUpdate.test.d.ts +1 -0
- package/build/builders/buildUpdate.test.js +130 -0
- package/build/builders/buildWhere.d.ts +6 -0
- package/build/builders/buildWhere.js +63 -0
- package/build/builders/buildWhere.test.d.ts +1 -0
- package/build/builders/buildWhere.test.js +154 -0
- package/build/builders/types.d.ts +87 -0
- package/build/builders/types.js +1 -0
- package/build/connection.d.ts +31 -0
- package/build/connection.js +206 -0
- package/build/index.d.ts +10 -0
- package/build/index.js +5 -0
- package/build/operators.d.ts +59 -0
- package/build/operators.js +44 -0
- package/build/orm.count.d.ts +14 -0
- package/build/orm.count.js +22 -0
- package/build/orm.createMany.d.ts +5 -0
- package/build/orm.createMany.js +26 -0
- package/build/orm.createOne.d.ts +5 -0
- package/build/orm.createOne.js +34 -0
- package/build/orm.d.ts +81 -0
- package/build/orm.deleteMany.d.ts +5 -0
- package/build/orm.deleteMany.js +26 -0
- package/build/orm.deleteOne.d.ts +5 -0
- package/build/orm.deleteOne.js +32 -0
- package/build/orm.findMany.d.ts +8 -0
- package/build/orm.findMany.js +64 -0
- package/build/orm.findOne.d.ts +5 -0
- package/build/orm.findOne.js +20 -0
- package/build/orm.js +243 -0
- package/build/orm.restrict.d.ts +6 -0
- package/build/orm.restrict.js +52 -0
- package/build/orm.updateMany.d.ts +5 -0
- package/build/orm.updateMany.js +29 -0
- package/build/orm.updateOne.d.ts +5 -0
- package/build/orm.updateOne.js +35 -0
- package/build/sql/countToSql.d.ts +3 -0
- package/build/sql/countToSql.js +11 -0
- package/build/sql/countToSql.test.d.ts +1 -0
- package/build/sql/countToSql.test.js +218 -0
- package/build/sql/createToSql.d.ts +3 -0
- package/build/sql/createToSql.js +27 -0
- package/build/sql/createToSql.test.d.ts +1 -0
- package/build/sql/createToSql.test.js +186 -0
- package/build/sql/deleteToSql.d.ts +3 -0
- package/build/sql/deleteToSql.js +15 -0
- package/build/sql/deleteToSql.test.d.ts +1 -0
- package/build/sql/deleteToSql.test.js +93 -0
- package/build/sql/findToSql.d.ts +3 -0
- package/build/sql/findToSql.js +33 -0
- package/build/sql/findToSql.test.d.ts +1 -0
- package/build/sql/findToSql.test.js +409 -0
- package/build/sql/updateToSql.d.ts +3 -0
- package/build/sql/updateToSql.js +16 -0
- package/build/sql/updateToSql.test.d.ts +1 -0
- package/build/sql/updateToSql.test.js +165 -0
- package/build/sql/util.d.ts +11 -0
- package/build/sql/util.js +36 -0
- package/build/sql/util.test.d.ts +1 -0
- package/build/sql/util.test.js +163 -0
- package/build/tests/connection.test.d.ts +1 -0
- package/build/tests/connection.test.js +304 -0
- package/build/tests/datatypes.test.d.ts +1 -0
- package/build/tests/datatypes.test.js +239 -0
- package/build/tests/operators.test.d.ts +1 -0
- package/build/tests/operators.test.js +125 -0
- package/build/tests/orm.count.middleware.test.d.ts +1 -0
- package/build/tests/orm.count.middleware.test.js +132 -0
- package/build/tests/orm.count.test-d.d.ts +1 -0
- package/build/tests/orm.count.test-d.js +60 -0
- package/build/tests/orm.count.test.d.ts +1 -0
- package/build/tests/orm.count.test.js +151 -0
- package/build/tests/orm.createMany.middleware.test.d.ts +1 -0
- package/build/tests/orm.createMany.middleware.test.js +63 -0
- package/build/tests/orm.createMany.test-d.d.ts +1 -0
- package/build/tests/orm.createMany.test-d.js +131 -0
- package/build/tests/orm.createMany.test.d.ts +1 -0
- package/build/tests/orm.createMany.test.js +392 -0
- package/build/tests/orm.createOne.middleware.test.d.ts +1 -0
- package/build/tests/orm.createOne.middleware.test.js +54 -0
- package/build/tests/orm.createOne.test-d.d.ts +1 -0
- package/build/tests/orm.createOne.test-d.js +113 -0
- package/build/tests/orm.createOne.test.d.ts +1 -0
- package/build/tests/orm.createOne.test.js +268 -0
- package/build/tests/orm.deleteMany.middleware.test.d.ts +1 -0
- package/build/tests/orm.deleteMany.middleware.test.js +77 -0
- package/build/tests/orm.deleteMany.test-d.d.ts +1 -0
- package/build/tests/orm.deleteMany.test-d.js +179 -0
- package/build/tests/orm.deleteMany.test.d.ts +1 -0
- package/build/tests/orm.deleteMany.test.js +394 -0
- package/build/tests/orm.deleteOne.middleware.test.d.ts +1 -0
- package/build/tests/orm.deleteOne.middleware.test.js +61 -0
- package/build/tests/orm.deleteOne.test-d.d.ts +1 -0
- package/build/tests/orm.deleteOne.test-d.js +179 -0
- package/build/tests/orm.deleteOne.test.d.ts +1 -0
- package/build/tests/orm.deleteOne.test.js +360 -0
- package/build/tests/orm.findMany.includeManyToMany.test.d.ts +1 -0
- package/build/tests/orm.findMany.includeManyToMany.test.js +335 -0
- package/build/tests/orm.findMany.includeManyToOne.test.d.ts +1 -0
- package/build/tests/orm.findMany.includeManyToOne.test.js +286 -0
- package/build/tests/orm.findMany.includeOneToMany.test.d.ts +1 -0
- package/build/tests/orm.findMany.includeOneToMany.test.js +530 -0
- package/build/tests/orm.findMany.middleware.test.d.ts +1 -0
- package/build/tests/orm.findMany.middleware.test.js +66 -0
- package/build/tests/orm.findMany.offsetLimit.test.d.ts +1 -0
- package/build/tests/orm.findMany.offsetLimit.test.js +108 -0
- package/build/tests/orm.findMany.orderBy.test.d.ts +1 -0
- package/build/tests/orm.findMany.orderBy.test.js +304 -0
- package/build/tests/orm.findMany.select.test.d.ts +1 -0
- package/build/tests/orm.findMany.select.test.js +278 -0
- package/build/tests/orm.findMany.test-d.d.ts +1 -0
- package/build/tests/orm.findMany.test-d.js +374 -0
- package/build/tests/orm.findMany.where.test.d.ts +1 -0
- package/build/tests/orm.findMany.where.test.js +383 -0
- package/build/tests/orm.findOne.middleware.test.d.ts +1 -0
- package/build/tests/orm.findOne.middleware.test.js +57 -0
- package/build/tests/orm.findOne.test-d.d.ts +1 -0
- package/build/tests/orm.findOne.test-d.js +377 -0
- package/build/tests/orm.findOne.test.d.ts +1 -0
- package/build/tests/orm.findOne.test.js +247 -0
- package/build/tests/orm.restrict.test-d.d.ts +1 -0
- package/build/tests/orm.restrict.test-d.js +105 -0
- package/build/tests/orm.restrict.test.d.ts +1 -0
- package/build/tests/orm.restrict.test.js +259 -0
- package/build/tests/orm.transact.test.d.ts +1 -0
- package/build/tests/orm.transact.test.js +48 -0
- package/build/tests/orm.updateMany.middleware.test.d.ts +1 -0
- package/build/tests/orm.updateMany.middleware.test.js +72 -0
- package/build/tests/orm.updateMany.test.d.ts +1 -0
- package/build/tests/orm.updateMany.test.js +210 -0
- package/build/tests/orm.updateOne.middleware.test.d.ts +1 -0
- package/build/tests/orm.updateOne.middleware.test.js +62 -0
- package/build/tests/orm.updateOne.test.d.ts +1 -0
- package/build/tests/orm.updateOne.test.js +209 -0
- package/build/tests/util/db.d.ts +1571 -0
- package/build/tests/util/db.js +10 -0
- package/build/tests/util/logger.d.ts +19 -0
- package/build/tests/util/logger.js +40 -0
- package/build/types/BaseFindParams.d.ts +1 -0
- package/build/types/BaseFindParams.js +1 -0
- package/build/types/CountParams.d.ts +16 -0
- package/build/types/CountParams.js +1 -0
- package/build/types/CountParams.test-d.d.ts +1 -0
- package/build/types/CountParams.test-d.js +89 -0
- package/build/types/CreateManyParams.d.ts +11 -0
- package/build/types/CreateManyParams.js +1 -0
- package/build/types/CreateManyParams.test-d.d.ts +1 -0
- package/build/types/CreateManyParams.test-d.js +83 -0
- package/build/types/CreateManyResult.d.ts +5 -0
- package/build/types/CreateManyResult.js +1 -0
- package/build/types/CreateManyResult.test-d.d.ts +1 -0
- package/build/types/CreateManyResult.test-d.js +22 -0
- package/build/types/CreateOneParams.d.ts +11 -0
- package/build/types/CreateOneParams.js +1 -0
- package/build/types/CreateOneParams.test-d.d.ts +1 -0
- package/build/types/CreateOneParams.test-d.js +56 -0
- package/build/types/CreateOneResult.d.ts +5 -0
- package/build/types/CreateOneResult.js +1 -0
- package/build/types/CreateOneResult.test-d.d.ts +1 -0
- package/build/types/CreateOneResult.test-d.js +23 -0
- package/build/types/CreateValues.d.ts +6 -0
- package/build/types/CreateValues.js +1 -0
- package/build/types/CreateValues.test-d.d.ts +1 -0
- package/build/types/CreateValues.test-d.js +60 -0
- package/build/types/DeleteManyResult.d.ts +5 -0
- package/build/types/DeleteManyResult.js +1 -0
- package/build/types/DeleteManyResult.test-d.d.ts +1 -0
- package/build/types/DeleteManyResult.test-d.js +23 -0
- package/build/types/DeleteOneResult.d.ts +5 -0
- package/build/types/DeleteOneResult.js +1 -0
- package/build/types/DeleteOneResult.test-d.d.ts +1 -0
- package/build/types/DeleteOneResult.test-d.js +23 -0
- package/build/types/DeleteParams.d.ts +7 -0
- package/build/types/DeleteParams.js +1 -0
- package/build/types/DeleteParams.test-d.d.ts +1 -0
- package/build/types/DeleteParams.test-d.js +46 -0
- package/build/types/FindParams.d.ts +22 -0
- package/build/types/FindParams.js +1 -0
- package/build/types/FindParams.test-d.d.ts +1 -0
- package/build/types/FindParams.test-d.js +107 -0
- package/build/types/FindResult.d.ts +13 -0
- package/build/types/FindResult.js +1 -0
- package/build/types/FindResult.test-d.d.ts +1 -0
- package/build/types/FindResult.test-d.js +30 -0
- package/build/types/IncludeClause.d.ts +5 -0
- package/build/types/IncludeClause.js +1 -0
- package/build/types/IncludeClause.test-d.d.ts +1 -0
- package/build/types/IncludeClause.test-d.js +69 -0
- package/build/types/Middleware.d.ts +51 -0
- package/build/types/Middleware.js +37 -0
- package/build/types/OptionalValues.d.ts +4 -0
- package/build/types/OptionalValues.js +1 -0
- package/build/types/OptionalValues.test-d.d.ts +1 -0
- package/build/types/OptionalValues.test-d.js +12 -0
- package/build/types/OrderByClause.d.ts +9 -0
- package/build/types/OrderByClause.js +1 -0
- package/build/types/OrderByClause.test-d.d.ts +1 -0
- package/build/types/OrderByClause.test-d.js +44 -0
- package/build/types/RequiredValues.d.ts +4 -0
- package/build/types/RequiredValues.js +1 -0
- package/build/types/RequiredValues.test-d.d.ts +1 -0
- package/build/types/RequiredValues.test-d.js +22 -0
- package/build/types/RestrictModels.d.ts +23 -0
- package/build/types/RestrictModels.js +1 -0
- package/build/types/RestrictModels.test-d.d.ts +1 -0
- package/build/types/RestrictModels.test-d.js +44 -0
- package/build/types/ReturningClause.d.ts +3 -0
- package/build/types/ReturningClause.js +1 -0
- package/build/types/ReturningClause.test-d.d.ts +1 -0
- package/build/types/ReturningClause.test-d.js +15 -0
- package/build/types/SelectClause.d.ts +3 -0
- package/build/types/SelectClause.js +1 -0
- package/build/types/SelectClause.test-d.d.ts +1 -0
- package/build/types/SelectClause.test-d.js +15 -0
- package/build/types/UpdateManyResult.d.ts +5 -0
- package/build/types/UpdateManyResult.js +1 -0
- package/build/types/UpdateManyResult.test-d.d.ts +1 -0
- package/build/types/UpdateManyResult.test-d.js +15 -0
- package/build/types/UpdateOneResult.d.ts +5 -0
- package/build/types/UpdateOneResult.js +1 -0
- package/build/types/UpdateOneResult.test-d.d.ts +1 -0
- package/build/types/UpdateOneResult.test-d.js +15 -0
- package/build/types/UpdateParams.d.ts +9 -0
- package/build/types/UpdateParams.js +1 -0
- package/build/types/UpdateParams.test-d.d.ts +1 -0
- package/build/types/UpdateParams.test-d.js +29 -0
- package/build/types/UpdateValues.d.ts +6 -0
- package/build/types/UpdateValues.js +1 -0
- package/build/types/UpdateValues.test-d.d.ts +1 -0
- package/build/types/UpdateValues.test-d.js +33 -0
- package/build/types/WhereClause.d.ts +29 -0
- package/build/types/WhereClause.js +1 -0
- package/build/types/WhereClause.test-d.d.ts +1 -0
- package/build/types/WhereClause.test-d.js +137 -0
- package/build/util/getIncludedToManySubqueries.d.ts +12 -0
- package/build/util/getIncludedToManySubqueries.js +63 -0
- package/build/util/getIncludedToManySubqueries.test.d.ts +1 -0
- package/build/util/getIncludedToManySubqueries.test.js +121 -0
- package/build/util/getLateralJoinValues.d.ts +4 -0
- package/build/util/getLateralJoinValues.js +30 -0
- package/build/util/getLateralJoinValues.test.d.ts +1 -0
- package/build/util/getLateralJoinValues.test.js +99 -0
- package/build/util/hasClauses.d.ts +1 -0
- package/build/util/hasClauses.js +9 -0
- package/build/util/hasClauses.test.d.ts +1 -0
- package/build/util/hasClauses.test.js +15 -0
- package/build/util/makeTableAlias.d.ts +1 -0
- package/build/util/makeTableAlias.js +10 -0
- package/build/util/makeTableAlias.test.d.ts +1 -0
- package/build/util/makeTableAlias.test.js +18 -0
- package/build/util/resultSchema.d.ts +10 -0
- package/build/util/resultSchema.js +42 -0
- package/build/util/rowToObject.d.ts +8 -0
- package/build/util/rowToObject.js +10 -0
- package/build/util/rowToObject.test.d.ts +1 -0
- package/build/util/rowToObject.test.js +68 -0
- package/package.json +67 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { FieldType, ModelDefinitions, ModelName, OperatorDefinitions } from "@casekit/orm2-schema";
|
|
2
|
+
import { UpdateParams } from "./UpdateParams.js";
|
|
3
|
+
export type UpdateOneResult<Models extends ModelDefinitions, Operators extends OperatorDefinitions, M extends ModelName<Models>, Q extends UpdateParams<Models, Operators, M>> = Q["returning"] extends NonNullable<Q["returning"]> ? {
|
|
4
|
+
[C in Q["returning"][number]]: FieldType<Models[M], C>;
|
|
5
|
+
} : number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expectTypeOf, test } from "vitest";
|
|
2
|
+
describe("UpdateOneResult", () => {
|
|
3
|
+
test("returns count when no returning clause", () => {
|
|
4
|
+
expectTypeOf().toEqualTypeOf();
|
|
5
|
+
});
|
|
6
|
+
test("returns array of selected fields when returning clause present", () => {
|
|
7
|
+
expectTypeOf().toEqualTypeOf();
|
|
8
|
+
});
|
|
9
|
+
test("handles complex field types", () => {
|
|
10
|
+
expectTypeOf().toEqualTypeOf();
|
|
11
|
+
});
|
|
12
|
+
test("does not allow invalid field names in returning clause", () => {
|
|
13
|
+
let _;
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ModelDefinitions, ModelName, OperatorDefinitions } from "@casekit/orm2-schema";
|
|
2
|
+
import { ReturningClause } from "./ReturningClause.js";
|
|
3
|
+
import { UpdateValues } from "./UpdateValues.js";
|
|
4
|
+
import { WhereClause } from "./WhereClause.js";
|
|
5
|
+
export type UpdateParams<Models extends ModelDefinitions, Operators extends OperatorDefinitions, M extends ModelName<Models>> = {
|
|
6
|
+
set: UpdateValues<Models[M]>;
|
|
7
|
+
where: WhereClause<Models, Operators, M>;
|
|
8
|
+
returning?: ReturningClause<Models[M]>;
|
|
9
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, test } from "vitest";
|
|
2
|
+
describe("UpdateParams", () => {
|
|
3
|
+
test("basic structure", () => {
|
|
4
|
+
const _ = {
|
|
5
|
+
set: { title: "hello world" },
|
|
6
|
+
where: { authorId: 1 },
|
|
7
|
+
returning: ["id", "title"],
|
|
8
|
+
};
|
|
9
|
+
});
|
|
10
|
+
test("returning clause is optional", () => {
|
|
11
|
+
const _ = {
|
|
12
|
+
set: { content: "hello" },
|
|
13
|
+
where: { id: 1 },
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
test("handles complex field types", () => {
|
|
17
|
+
const _ = {
|
|
18
|
+
set: { tags: ["hello", "world"], deletedAt: null },
|
|
19
|
+
where: { title: "hello" },
|
|
20
|
+
returning: ["id", "tags", "deletedAt"],
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
test("rejects invalid field names", () => {
|
|
24
|
+
const _ = {
|
|
25
|
+
// @ts-expect-error invalid field
|
|
26
|
+
invalid: "wrong",
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DeepReadonly } from "ts-essentials";
|
|
2
|
+
import { FieldName, FieldType, ModelDefinition } from "@casekit/orm2-schema";
|
|
3
|
+
import { Simplify } from "@casekit/toolbox";
|
|
4
|
+
export type UpdateValues<Model extends ModelDefinition> = DeepReadonly<Simplify<{
|
|
5
|
+
[K in FieldName<Model>]?: FieldType<Model, K>;
|
|
6
|
+
}>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, test } from "vitest";
|
|
2
|
+
describe("UpdateValues", () => {
|
|
3
|
+
test("all fields are optional", () => {
|
|
4
|
+
const _ = {};
|
|
5
|
+
});
|
|
6
|
+
test("handles nullable fields", () => {
|
|
7
|
+
const _ = {
|
|
8
|
+
backgroundColorValue: null,
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
test("handles arrays", () => {
|
|
12
|
+
const _ = {
|
|
13
|
+
tags: ["a", "b", "c"],
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
test("handles jsonb fields", () => {
|
|
17
|
+
const _ = {
|
|
18
|
+
metadata: {
|
|
19
|
+
foo: "a",
|
|
20
|
+
bar: [
|
|
21
|
+
{ baz: "good", quux: true },
|
|
22
|
+
{ baz: "bad", quux: false },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
test("rejects invalid field names", () => {
|
|
28
|
+
const _ = {
|
|
29
|
+
// @ts-expect-error invalid field
|
|
30
|
+
invalid: "wrong",
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { DeepReadonly } from "ts-essentials";
|
|
2
|
+
import { FieldName, FieldType, ModelDefinition, ModelDefinitions, ModelName, OperatorDefinitions } from "@casekit/orm2-schema";
|
|
3
|
+
import { $and, $eq, $gt, $gte, $ilike, $in, $is, $like, $lt, $lte, $ne, $not, $or } from "#operators.js";
|
|
4
|
+
export interface DefaultOperators<T> {
|
|
5
|
+
[$eq]?: T;
|
|
6
|
+
[$ne]?: T;
|
|
7
|
+
[$not]?: null | true | false;
|
|
8
|
+
[$is]?: null | true | false;
|
|
9
|
+
[$gt]?: T;
|
|
10
|
+
[$gte]?: T;
|
|
11
|
+
[$lt]?: T;
|
|
12
|
+
[$lte]?: T;
|
|
13
|
+
[$in]?: T[];
|
|
14
|
+
[$like]?: string;
|
|
15
|
+
[$ilike]?: string;
|
|
16
|
+
}
|
|
17
|
+
export type ReadonlyArrays<T> = T extends (infer E)[] ? readonly E[] : T extends Date ? Date : T extends object ? {
|
|
18
|
+
readonly [K in keyof T]: ReadonlyArrays<T[K]>;
|
|
19
|
+
} : T;
|
|
20
|
+
export type WhereClauseValue<Model extends ModelDefinition, Operators extends OperatorDefinitions, C extends FieldName<Model>, T = DeepReadonly<FieldType<Model, C>>> = T | null | DefaultOperators<T> | (Operators["where"] extends never ? never : {
|
|
21
|
+
[K in Extract<keyof Operators["where"], symbol>]?: Parameters<NonNullable<Operators["where"]>[K]>[1];
|
|
22
|
+
});
|
|
23
|
+
export type WhereClause<Models extends ModelDefinitions, Operators extends OperatorDefinitions, M extends ModelName<Models>, _Model extends ModelDefinition = Models[M]> = {
|
|
24
|
+
[C in FieldName<_Model>]?: WhereClauseValue<_Model, Operators, C>;
|
|
25
|
+
} & {
|
|
26
|
+
[$and]?: Array<WhereClause<Models, Operators, M>>;
|
|
27
|
+
[$or]?: Array<WhereClause<Models, Operators, M>>;
|
|
28
|
+
[$not]?: WhereClause<Models, Operators, M>;
|
|
29
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { $and, $eq, $gt, $gte, $ilike, $in, $is, $like, $lt, $lte, $ne, $not, $or, } from "#operators.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, test } from "vitest";
|
|
2
|
+
import { sql } from "@casekit/sql";
|
|
3
|
+
import { $and, $gt, $gte, $ilike, $in, $is, $like, $lt, $not, $or, } from "#operators.js";
|
|
4
|
+
describe("WhereClause", () => {
|
|
5
|
+
test("basic value comparisons", () => {
|
|
6
|
+
const _ = {
|
|
7
|
+
name: "John",
|
|
8
|
+
createdAt: { [$gt]: new Date("2000-01-01") },
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
test("logical operators", () => {
|
|
12
|
+
const _ = {
|
|
13
|
+
[$or]: [
|
|
14
|
+
{ name: { [$like]: "John%" } },
|
|
15
|
+
{
|
|
16
|
+
[$and]: [
|
|
17
|
+
{ createdAt: { [$gte]: new Date("2000-01-01") } },
|
|
18
|
+
{ createdAt: { [$lt]: new Date("2024-01-01") } },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
test("null checks with $is", () => {
|
|
25
|
+
const _ = {
|
|
26
|
+
name: { [$is]: null },
|
|
27
|
+
createdAt: { [$is]: null },
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
test("null checks with $not", () => {
|
|
31
|
+
const _ = {
|
|
32
|
+
name: { [$is]: null },
|
|
33
|
+
createdAt: { [$not]: null },
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
test("boolean checks with $is", () => {
|
|
37
|
+
const _ = {
|
|
38
|
+
name: { [$is]: null },
|
|
39
|
+
createdAt: { [$is]: false },
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
test("boolean checks with $not", () => {
|
|
43
|
+
const _ = {
|
|
44
|
+
name: { [$is]: null },
|
|
45
|
+
createdAt: { [$not]: true },
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
test("string operations", () => {
|
|
49
|
+
const _ = {
|
|
50
|
+
name: {
|
|
51
|
+
[$like]: "%doe%",
|
|
52
|
+
[$ilike]: "JOHN%",
|
|
53
|
+
},
|
|
54
|
+
email: { [$in]: ["john@example.com", "jane@example.com"] },
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
test("compound conditions", () => {
|
|
58
|
+
const _ = {
|
|
59
|
+
[$and]: [
|
|
60
|
+
{
|
|
61
|
+
createdAt: {
|
|
62
|
+
[$gte]: new Date("2000-01-01"),
|
|
63
|
+
[$lt]: new Date("2024-01-01"),
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
[$or]: [
|
|
68
|
+
{ email: { [$like]: "%@company.com" } },
|
|
69
|
+
{ role: { [$in]: ["admin", "user"] } },
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
test("custom operators", () => {
|
|
76
|
+
const $startswith = Symbol("startswith");
|
|
77
|
+
const operators = {
|
|
78
|
+
where: {
|
|
79
|
+
[$startswith]: ({ table, column }, value) => sql `${table}.${column} LIKE ${value}%`,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
const _ = {
|
|
83
|
+
name: { [$startswith]: "John" },
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
describe("invalid queries", () => {
|
|
87
|
+
test("wrong type", () => {
|
|
88
|
+
const _ = {
|
|
89
|
+
// @ts-expect-error name should be string
|
|
90
|
+
name: 621,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
test("non existent column", () => {
|
|
94
|
+
const _ = {
|
|
95
|
+
// @ts-expect-error non existent column
|
|
96
|
+
wrong: "x",
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
test("invalid $in value", () => {
|
|
100
|
+
const _ = {
|
|
101
|
+
role: {
|
|
102
|
+
// @ts-expect-error invalid $in value
|
|
103
|
+
[$in]: ["admin", "user", "wrong"],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
test("invalid $is value", () => {
|
|
108
|
+
const _ = {
|
|
109
|
+
name: {
|
|
110
|
+
// @ts-expect-error $is only works with null or boolean
|
|
111
|
+
[$is]: "test",
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
test("invalid $like value", () => {
|
|
116
|
+
const _ = {
|
|
117
|
+
counter: {
|
|
118
|
+
// @ts-expect-error $like only works with string
|
|
119
|
+
[$like]: 123,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
test("invalid $and value", () => {
|
|
124
|
+
const _ = {
|
|
125
|
+
[$and]:
|
|
126
|
+
// @ts-expect-error should be an array
|
|
127
|
+
{ createdAt: { [$gt]: Date } },
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
test("invalid $not value", () => {
|
|
131
|
+
const _ = {
|
|
132
|
+
// @ts-expect-error $not should take an object, not an array
|
|
133
|
+
[$not]: [{ age: 21 }],
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NormalizedConfig } from "@casekit/orm2-config";
|
|
2
|
+
import { ModelDefinition, OperatorDefinitions } from "@casekit/orm2-schema";
|
|
3
|
+
import { FindParams } from "../types/FindParams.js";
|
|
4
|
+
export type ToManyRelationSubquery = {
|
|
5
|
+
query: FindParams<Record<string, Required<ModelDefinition>>, OperatorDefinitions, string>;
|
|
6
|
+
modelName: string;
|
|
7
|
+
from: string[];
|
|
8
|
+
to: string[];
|
|
9
|
+
path: string[];
|
|
10
|
+
extract: (rows: Record<string, unknown>[]) => unknown[];
|
|
11
|
+
};
|
|
12
|
+
export declare const getIncludedToManySubqueries: (config: NormalizedConfig, modelName: string, query: FindParams<Record<string, Required<ModelDefinition>>, OperatorDefinitions, string>, path?: string[]) => ToManyRelationSubquery[];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { identity } from "es-toolkit";
|
|
2
|
+
import { getModel, getRelation } from "@casekit/orm2-config";
|
|
3
|
+
export const getIncludedToManySubqueries = (config, modelName, query, path = []) => {
|
|
4
|
+
const relationQueries = [];
|
|
5
|
+
for (const [relationName, subquery] of Object.entries(query.include ?? {})) {
|
|
6
|
+
const model = getModel(config.models, modelName);
|
|
7
|
+
const relation = getRelation(model, relationName);
|
|
8
|
+
if (relation.type === "1:N") {
|
|
9
|
+
relationQueries.push({
|
|
10
|
+
modelName: relation.model,
|
|
11
|
+
query: subquery,
|
|
12
|
+
from: relation.from.fields,
|
|
13
|
+
to: relation.to.fields,
|
|
14
|
+
extract: identity,
|
|
15
|
+
path: [...path, relation.name],
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
else if (relation.type === "N:N") {
|
|
19
|
+
// for N:N relations, we create a lateral join from the parent model
|
|
20
|
+
// to the join model via its 'fromRelation', rather than directly tothe relation
|
|
21
|
+
// model itself.
|
|
22
|
+
const throughModel = getModel(config.models, relation.through.model);
|
|
23
|
+
const fromRelation = getRelation(throughModel, relation.through.fromRelation);
|
|
24
|
+
if (fromRelation.type !== "N:1") {
|
|
25
|
+
throw new Error(`Expected relation ${relation.through.fromRelation} to be a N:1 relation, but got ${fromRelation.type}`);
|
|
26
|
+
}
|
|
27
|
+
const toRelation = getRelation(throughModel, relation.through.toRelation);
|
|
28
|
+
if (toRelation.type !== "N:1") {
|
|
29
|
+
throw new Error(`Expected relation ${relation.through.toRelation} to be a N:1 relation, but got ${toRelation.type}`);
|
|
30
|
+
}
|
|
31
|
+
const from = fromRelation.to.fields;
|
|
32
|
+
const to = fromRelation.from.fields;
|
|
33
|
+
// instead of querying directly for the relation model,
|
|
34
|
+
// we wrap the query in a query of the join model and
|
|
35
|
+
// include the query of the relation model as a 1:N
|
|
36
|
+
// subquery
|
|
37
|
+
const query = {
|
|
38
|
+
select: throughModel.primaryKey.map((k) => k.field),
|
|
39
|
+
include: {
|
|
40
|
+
[toRelation.name]: subquery,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
// because we're going to be running a query on the join model
|
|
44
|
+
// rather than directly on the relation model, the results
|
|
45
|
+
// will be for the join model, so we need to map over them
|
|
46
|
+
// and extract the embedded relation model results.
|
|
47
|
+
const extract = (rows) => rows.map((r) => r[toRelation.name]);
|
|
48
|
+
relationQueries.push({
|
|
49
|
+
modelName: relation.through.model,
|
|
50
|
+
query,
|
|
51
|
+
path: [...path, relation.name],
|
|
52
|
+
from,
|
|
53
|
+
to,
|
|
54
|
+
extract,
|
|
55
|
+
});
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
57
|
+
}
|
|
58
|
+
else if (relation.type === "N:1") {
|
|
59
|
+
relationQueries.push(...getIncludedToManySubqueries(config, relation.model, subquery, [...path, relation.name]));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return relationQueries;
|
|
63
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { identity } from "es-toolkit";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { createTestDB } from "../tests/util/db.js";
|
|
4
|
+
import { getIncludedToManySubqueries } from "./getIncludedToManySubqueries.js";
|
|
5
|
+
describe("getIncludedToManySubqueries", () => {
|
|
6
|
+
const { db } = createTestDB();
|
|
7
|
+
test("returns empty array when no includes specified", () => {
|
|
8
|
+
const result = getIncludedToManySubqueries(db.config, "user", {
|
|
9
|
+
select: ["id", "name"],
|
|
10
|
+
});
|
|
11
|
+
expect(result).toEqual([]);
|
|
12
|
+
});
|
|
13
|
+
test("processes 1:N relations correctly", () => {
|
|
14
|
+
const result = getIncludedToManySubqueries(db.config, "user", {
|
|
15
|
+
select: ["id", "name"],
|
|
16
|
+
include: {
|
|
17
|
+
posts: {
|
|
18
|
+
select: ["id", "title"],
|
|
19
|
+
where: { deletedAt: null },
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
expect(result).toHaveLength(1);
|
|
24
|
+
expect(result[0]).toMatchObject({
|
|
25
|
+
modelName: "post",
|
|
26
|
+
from: ["id"],
|
|
27
|
+
to: ["authorId"],
|
|
28
|
+
path: ["posts"],
|
|
29
|
+
query: {
|
|
30
|
+
select: ["id", "title"],
|
|
31
|
+
where: { deletedAt: null },
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
expect(result[0].extract).toBe(identity);
|
|
35
|
+
});
|
|
36
|
+
test("processes N:N relations correctly", () => {
|
|
37
|
+
const result = getIncludedToManySubqueries(db.config, "user", {
|
|
38
|
+
select: ["id", "name"],
|
|
39
|
+
include: {
|
|
40
|
+
friends: {
|
|
41
|
+
select: ["id", "name"],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
expect(result).toHaveLength(1);
|
|
46
|
+
expect(result[0]).toMatchObject({
|
|
47
|
+
modelName: "friendship",
|
|
48
|
+
from: ["id"],
|
|
49
|
+
to: ["userId"],
|
|
50
|
+
path: ["friends"],
|
|
51
|
+
query: {
|
|
52
|
+
select: ["userId", "friendId"],
|
|
53
|
+
include: {
|
|
54
|
+
friend: {
|
|
55
|
+
select: ["id", "name"],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
const mockRows = [
|
|
61
|
+
{ friend: { id: 1, name: "Friend 1" } },
|
|
62
|
+
{ friend: { id: 2, name: "Friend 2" } },
|
|
63
|
+
];
|
|
64
|
+
expect(result[0].extract(mockRows)).toEqual([
|
|
65
|
+
{ id: 1, name: "Friend 1" },
|
|
66
|
+
{ id: 2, name: "Friend 2" },
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
test("processes nested N:1 relations with to-many relations correctly", () => {
|
|
70
|
+
const result = getIncludedToManySubqueries(db.config, "like", {
|
|
71
|
+
select: ["id", "userId"],
|
|
72
|
+
include: {
|
|
73
|
+
post: {
|
|
74
|
+
select: ["id"],
|
|
75
|
+
include: {
|
|
76
|
+
likes: {
|
|
77
|
+
select: ["id", "userId"],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
expect(result).toHaveLength(1);
|
|
84
|
+
expect(result[0]).toMatchObject({
|
|
85
|
+
modelName: "like",
|
|
86
|
+
path: ["post", "likes"],
|
|
87
|
+
query: {
|
|
88
|
+
select: ["id", "userId"],
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
test("handles multiple to-many relations at the same level", () => {
|
|
93
|
+
const result = getIncludedToManySubqueries(db.config, "post", {
|
|
94
|
+
select: ["id", "title"],
|
|
95
|
+
include: {
|
|
96
|
+
likes: {
|
|
97
|
+
select: ["id", "userId"],
|
|
98
|
+
},
|
|
99
|
+
author: {
|
|
100
|
+
select: ["id"],
|
|
101
|
+
include: {
|
|
102
|
+
posts: {
|
|
103
|
+
select: ["id", "title"],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
expect(result).toHaveLength(2);
|
|
110
|
+
expect(result).toEqual(expect.arrayContaining([
|
|
111
|
+
expect.objectContaining({
|
|
112
|
+
modelName: "like",
|
|
113
|
+
path: ["likes"],
|
|
114
|
+
}),
|
|
115
|
+
expect.objectContaining({
|
|
116
|
+
modelName: "post",
|
|
117
|
+
path: ["author", "posts"],
|
|
118
|
+
}),
|
|
119
|
+
]));
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isEqual, isNil, uniqWith } from "es-toolkit";
|
|
2
|
+
import { get } from "es-toolkit/compat";
|
|
3
|
+
export const getLateralJoinValues = (results, path, from, to) => {
|
|
4
|
+
const all = results.flatMap((result) => {
|
|
5
|
+
const parent = path.length === 0
|
|
6
|
+
? result
|
|
7
|
+
: get(result, path);
|
|
8
|
+
if (isNil(parent)) {
|
|
9
|
+
// if the relation is a join from an optional N:1 relation of
|
|
10
|
+
// the query's top level model, then the value being joined from
|
|
11
|
+
// make not exist on the result. in that case we can't do a lateral
|
|
12
|
+
// join so we ignore it.
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
return [
|
|
16
|
+
Object.fromEntries(to.map((f, index) => [f, parent[from[index]]])),
|
|
17
|
+
];
|
|
18
|
+
});
|
|
19
|
+
const unique = uniqWith(all, isEqual);
|
|
20
|
+
const values = Object.fromEntries(to.map((f) => [f, []]));
|
|
21
|
+
for (const v of unique) {
|
|
22
|
+
for (const f of to) {
|
|
23
|
+
values[f].push(v[f]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return to.map((f) => ({
|
|
27
|
+
field: f,
|
|
28
|
+
values: values[f] ?? [],
|
|
29
|
+
}));
|
|
30
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { getLateralJoinValues } from "./getLateralJoinValues.js";
|
|
3
|
+
describe("getLateralJoinValues", () => {
|
|
4
|
+
test("should handle flat objects with direct mapping", () => {
|
|
5
|
+
const results = [
|
|
6
|
+
{ id: 1, name: "John" },
|
|
7
|
+
{ id: 2, name: "Jane" },
|
|
8
|
+
{ id: 2, name: "Jane" }, // Duplicate to test uniqueness
|
|
9
|
+
];
|
|
10
|
+
const result = getLateralJoinValues(results, [], // empty path for top-level properties
|
|
11
|
+
["id", "name"], ["userId", "userName"]);
|
|
12
|
+
expect(result).toEqual([
|
|
13
|
+
{
|
|
14
|
+
field: "userId",
|
|
15
|
+
values: [1, 2],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
field: "userName",
|
|
19
|
+
values: ["John", "Jane"],
|
|
20
|
+
},
|
|
21
|
+
]);
|
|
22
|
+
});
|
|
23
|
+
test("should handle nested objects with path", () => {
|
|
24
|
+
const results = [
|
|
25
|
+
{ user: { id: 1, name: "John" } },
|
|
26
|
+
{ user: { id: 2, name: "Jane" } },
|
|
27
|
+
];
|
|
28
|
+
const result = getLateralJoinValues(results, ["user"], ["id", "name"], ["userId", "userName"]);
|
|
29
|
+
expect(result).toEqual([
|
|
30
|
+
{
|
|
31
|
+
field: "userId",
|
|
32
|
+
values: [1, 2],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
field: "userName",
|
|
36
|
+
values: ["John", "Jane"],
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
test("should handle missing nested objects", () => {
|
|
41
|
+
const results = [
|
|
42
|
+
{ user: { id: 1, name: "John" } },
|
|
43
|
+
{ user: null },
|
|
44
|
+
{ user: { id: 2, name: "Jane" } },
|
|
45
|
+
];
|
|
46
|
+
const result = getLateralJoinValues(results, ["user"], ["id", "name"], ["userId", "userName"]);
|
|
47
|
+
expect(result).toEqual([
|
|
48
|
+
{
|
|
49
|
+
field: "userId",
|
|
50
|
+
values: [1, 2],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
field: "userName",
|
|
54
|
+
values: ["John", "Jane"],
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
test(" handle deeply nested paths", () => {
|
|
59
|
+
const results = [
|
|
60
|
+
{ data: { user: { profile: { id: 1, name: "John" } } } },
|
|
61
|
+
{ data: { user: { profile: { id: 2, name: "Jane" } } } },
|
|
62
|
+
];
|
|
63
|
+
const result = getLateralJoinValues(results, ["data", "user", "profile"], ["id", "name"], ["userId", "userName"]);
|
|
64
|
+
expect(result).toEqual([
|
|
65
|
+
{
|
|
66
|
+
field: "userId",
|
|
67
|
+
values: [1, 2],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
field: "userName",
|
|
71
|
+
values: ["John", "Jane"],
|
|
72
|
+
},
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
test("should handle empty results array", () => {
|
|
76
|
+
const results = [];
|
|
77
|
+
const result = getLateralJoinValues(results, ["user"], ["id", "name"], ["userId", "userName"]);
|
|
78
|
+
expect(result).toEqual([
|
|
79
|
+
{
|
|
80
|
+
field: "userId",
|
|
81
|
+
values: [],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
field: "userName",
|
|
85
|
+
values: [],
|
|
86
|
+
},
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
test("should handle single field mapping", () => {
|
|
90
|
+
const results = [{ id: 1 }, { id: 2 }];
|
|
91
|
+
const result = getLateralJoinValues(results, [], ["id"], ["userId"]);
|
|
92
|
+
expect(result).toEqual([
|
|
93
|
+
{
|
|
94
|
+
field: "userId",
|
|
95
|
+
values: [1, 2],
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const hasClauses: <T>(clause: T) => clause is NonNullable<T>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|