joist-core 2.3.0-next.99 → 2.4.0
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/EntityManager.cjs +1 -1
- package/build/EntityManager.cjs.map +1 -1
- package/build/EntityManager.d.cts +9 -8
- package/build/EntityManager.d.cts.map +1 -1
- package/build/EntityManager.d.mts +9 -8
- package/build/EntityManager.d.mts.map +1 -1
- package/build/EntityManager.js +1 -1
- package/build/EntityManager.js.map +1 -1
- package/build/dataloaders/findCountDataLoader.cjs +2 -0
- package/build/dataloaders/findCountDataLoader.cjs.map +1 -1
- package/build/dataloaders/findCountDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findCountDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findCountDataLoader.js +2 -0
- package/build/dataloaders/findCountDataLoader.js.map +1 -1
- package/build/dataloaders/findDataLoader.cjs +1 -0
- package/build/dataloaders/findDataLoader.cjs.map +1 -1
- package/build/dataloaders/findDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findDataLoader.js +2 -1
- package/build/dataloaders/findDataLoader.js.map +1 -1
- package/build/dataloaders/findIdsDataLoader.cjs +2 -0
- package/build/dataloaders/findIdsDataLoader.cjs.map +1 -1
- package/build/dataloaders/findIdsDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findIdsDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findIdsDataLoader.js +2 -0
- package/build/dataloaders/findIdsDataLoader.js.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.cjs +2 -0
- package/build/dataloaders/findPaginatedDataLoader.cjs.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.js +2 -0
- package/build/dataloaders/findPaginatedDataLoader.js.map +1 -1
- package/build/index.cjs +6 -3
- package/build/index.cjs.map +1 -1
- package/build/index.d.cts +6 -4
- package/build/index.d.cts.map +1 -1
- package/build/index.d.mts +6 -4
- package/build/index.d.mts.map +1 -1
- package/build/index.js +6 -5
- package/build/index.js.map +1 -1
- package/build/keys.cjs +6 -1
- package/build/keys.cjs.map +1 -1
- package/build/keys.d.cts +3 -1
- package/build/keys.d.cts.map +1 -1
- package/build/keys.d.mts +3 -1
- package/build/keys.d.mts.map +1 -1
- package/build/keys.js +6 -2
- package/build/keys.js.map +1 -1
- package/build/queries/find/QueryParser.cjs +1 -2
- package/build/queries/find/QueryParser.cjs.map +1 -1
- package/build/queries/find/QueryParser.d.cts.map +1 -1
- package/build/queries/find/QueryParser.d.mts.map +1 -1
- package/build/queries/find/QueryParser.js +2 -3
- package/build/queries/find/QueryParser.js.map +1 -1
- package/build/queries/find/QueryVisitor.cjs +17 -0
- package/build/queries/find/QueryVisitor.cjs.map +1 -1
- package/build/queries/find/QueryVisitor.d.cts +3 -1
- package/build/queries/find/QueryVisitor.d.cts.map +1 -1
- package/build/queries/find/QueryVisitor.d.mts +3 -1
- package/build/queries/find/QueryVisitor.d.mts.map +1 -1
- package/build/queries/find/QueryVisitor.js +17 -1
- package/build/queries/find/QueryVisitor.js.map +1 -1
- package/build/queries/sql/Expr.cjs +18 -1
- package/build/queries/sql/Expr.cjs.map +1 -1
- package/build/queries/sql/Expr.d.cts +13 -3
- package/build/queries/sql/Expr.d.cts.map +1 -1
- package/build/queries/sql/Expr.d.mts +13 -3
- package/build/queries/sql/Expr.d.mts.map +1 -1
- package/build/queries/sql/Expr.js +18 -2
- package/build/queries/sql/Expr.js.map +1 -1
- package/build/queries/sql/JoinTree.cjs +72 -0
- package/build/queries/sql/JoinTree.cjs.map +1 -0
- package/build/queries/sql/JoinTree.d.cts +107 -0
- package/build/queries/sql/JoinTree.d.cts.map +1 -0
- package/build/queries/sql/JoinTree.d.mts +107 -0
- package/build/queries/sql/JoinTree.d.mts.map +1 -0
- package/build/queries/sql/JoinTree.js +71 -0
- package/build/queries/sql/JoinTree.js.map +1 -0
- package/build/queries/sql/Tables.cjs +103 -6
- package/build/queries/sql/Tables.cjs.map +1 -1
- package/build/queries/sql/Tables.d.cts +72 -13
- package/build/queries/sql/Tables.d.cts.map +1 -1
- package/build/queries/sql/Tables.d.mts +72 -13
- package/build/queries/sql/Tables.d.mts.map +1 -1
- package/build/queries/sql/Tables.js +101 -10
- package/build/queries/sql/Tables.js.map +1 -1
- package/build/queries/sql/custom.cjs +52 -0
- package/build/queries/sql/custom.cjs.map +1 -0
- package/build/queries/sql/custom.d.cts +85 -0
- package/build/queries/sql/custom.d.cts.map +1 -0
- package/build/queries/sql/custom.d.mts +85 -0
- package/build/queries/sql/custom.d.mts.map +1 -0
- package/build/queries/sql/custom.js +49 -0
- package/build/queries/sql/custom.js.map +1 -0
- package/build/queries/sql/execute.cjs +68 -28
- package/build/queries/sql/execute.cjs.map +1 -1
- package/build/queries/sql/execute.d.cts +110 -11
- package/build/queries/sql/execute.d.cts.map +1 -1
- package/build/queries/sql/execute.d.mts +110 -11
- package/build/queries/sql/execute.d.mts.map +1 -1
- package/build/queries/sql/execute.js +69 -29
- package/build/queries/sql/execute.js.map +1 -1
- package/build/queries/sql/expressions/types.d.cts +5 -5
- package/build/queries/sql/expressions/types.d.cts.map +1 -1
- package/build/queries/sql/expressions/types.d.mts +5 -5
- package/build/queries/sql/expressions/types.d.mts.map +1 -1
- package/build/queries/sql/keywords.cjs +1 -1
- package/build/queries/sql/keywords.cjs.map +1 -1
- package/build/queries/sql/keywords.d.cts +1 -1
- package/build/queries/sql/keywords.d.mts +1 -1
- package/build/queries/sql/keywords.js +1 -1
- package/build/queries/sql/keywords.js.map +1 -1
- package/build/queries/sql/query.cjs +100 -38
- package/build/queries/sql/query.cjs.map +1 -1
- package/build/queries/sql/query.d.cts +96 -46
- package/build/queries/sql/query.d.cts.map +1 -1
- package/build/queries/sql/query.d.mts +96 -46
- package/build/queries/sql/query.d.mts.map +1 -1
- package/build/queries/sql/query.js +102 -40
- package/build/queries/sql/query.js.map +1 -1
- package/build/queries/valueFilters.cjs +28 -36
- package/build/queries/valueFilters.cjs.map +1 -1
- package/build/queries/valueFilters.d.cts +2 -4
- package/build/queries/valueFilters.d.cts.map +1 -1
- package/build/queries/valueFilters.d.mts +2 -4
- package/build/queries/valueFilters.d.mts.map +1 -1
- package/build/queries/valueFilters.js +29 -36
- package/build/queries/valueFilters.js.map +1 -1
- package/build/serde/serde.cjs.map +1 -1
- package/build/serde/serde.js.map +1 -1
- package/package.json +2 -2
|
@@ -7,7 +7,7 @@ const require_Entity = require("../Entity.cjs");
|
|
|
7
7
|
let joist_utils = require("joist-utils");
|
|
8
8
|
//#region src/queries/valueFilters.ts
|
|
9
9
|
/** Parses an entity filter, which could be "just an id", an array of ids, or a nested filter. */
|
|
10
|
-
function parseEntityFilter(
|
|
10
|
+
function parseEntityFilter(_meta, filter) {
|
|
11
11
|
if (filter === void 0) return;
|
|
12
12
|
else if (require_queries_find_Aliases.isAlias(filter)) return;
|
|
13
13
|
else if (require_queries_find_scopes.isScope(filter)) return {
|
|
@@ -22,13 +22,17 @@ function parseEntityFilter(meta, filter) {
|
|
|
22
22
|
else if (typeof filter === "boolean") return filter ? { kind: "not-null" } : { kind: "is-null" };
|
|
23
23
|
else if (Array.isArray(filter)) return {
|
|
24
24
|
kind: "in",
|
|
25
|
-
value: filter.
|
|
26
|
-
|
|
25
|
+
value: filter.flatMap((value) => {
|
|
26
|
+
if (!require_Entity.isEntity(value)) return [value];
|
|
27
|
+
return isNotInDb(value) ? [] : [value.idTagged];
|
|
27
28
|
})
|
|
28
29
|
};
|
|
29
|
-
else if (require_Entity.isEntity(filter)) return {
|
|
30
|
+
else if (require_Entity.isEntity(filter)) return isNotInDb(filter) ? {
|
|
31
|
+
kind: "in",
|
|
32
|
+
value: []
|
|
33
|
+
} : {
|
|
30
34
|
kind: "eq",
|
|
31
|
-
value: filter.
|
|
35
|
+
value: filter.idTagged
|
|
32
36
|
};
|
|
33
37
|
else if (typeof filter === "object") {
|
|
34
38
|
const keys = Object.keys(filter);
|
|
@@ -40,9 +44,9 @@ function parseEntityFilter(meta, filter) {
|
|
|
40
44
|
kind: "ne",
|
|
41
45
|
value
|
|
42
46
|
};
|
|
43
|
-
else if (require_Entity.isEntity(value)) return {
|
|
47
|
+
else if (require_Entity.isEntity(value)) return isNotInDb(value) ? { kind: "not-null" } : {
|
|
44
48
|
kind: "ne",
|
|
45
|
-
value: value.
|
|
49
|
+
value: value.idTagged
|
|
46
50
|
};
|
|
47
51
|
else throw new Error(`Unsupported "ne" value ${value}`);
|
|
48
52
|
}
|
|
@@ -54,14 +58,17 @@ function parseEntityFilter(meta, filter) {
|
|
|
54
58
|
kind: "eq",
|
|
55
59
|
value
|
|
56
60
|
};
|
|
57
|
-
else if (require_Entity.isEntity(value)) return {
|
|
61
|
+
else if (require_Entity.isEntity(value)) return isNotInDb(value) ? {
|
|
62
|
+
kind: "in",
|
|
63
|
+
value: []
|
|
64
|
+
} : {
|
|
58
65
|
kind: "eq",
|
|
59
|
-
value: value.
|
|
66
|
+
value: value.idTagged
|
|
60
67
|
};
|
|
61
68
|
else return parseValueFilter(value)[0];
|
|
62
69
|
}
|
|
63
70
|
const subFilter = {};
|
|
64
|
-
for (const [key, value] of Object.entries(filter)) if (value && typeof value === "object" && !(0, joist_utils.isPlainObject)(value) && "idTaggedMaybe" in value) subFilter[key] = value.idTaggedMaybe
|
|
71
|
+
for (const [key, value] of Object.entries(filter)) if (value && typeof value === "object" && !require_Entity.isEntity(value) && !(0, joist_utils.isPlainObject)(value) && "idTaggedMaybe" in value) subFilter[key] = value.idTaggedMaybe === void 0 ? [] : value.idTaggedMaybe;
|
|
65
72
|
else subFilter[key] = value;
|
|
66
73
|
return {
|
|
67
74
|
kind: "join",
|
|
@@ -208,38 +215,23 @@ function mapToDb(column, filter) {
|
|
|
208
215
|
function makeLike(search) {
|
|
209
216
|
return search ? `%${search.replace(/\s+/g, "%")}%` : void 0;
|
|
210
217
|
}
|
|
211
|
-
/** Recognizes placeholder IDs used when a filter contains an unpersisted entity. */
|
|
212
|
-
function isNilIdValue(value) {
|
|
213
|
-
return value === -1 || value === "00000000-0000-0000-0000-000000000000";
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* We use this value if users include new (id-less) entities as em.find conditions.
|
|
217
|
-
*
|
|
218
|
-
* The idea is that this condition would never be met, but we still want to do the em.find
|
|
219
|
-
* query in case it's in an `OR` clause that would match false, but some other part of the
|
|
220
|
-
* clause would match. I.e. instead of just skipping the DB query all together, which is
|
|
221
|
-
* also something we could consider doing.
|
|
222
|
-
*
|
|
223
|
-
* For int IDs we use -1, and for uuid IDs, we use the nil UUID value:
|
|
224
|
-
*
|
|
225
|
-
* https://en.wikipedia.org/wiki/Universally_unique_identifier#Nil_UUID
|
|
226
|
-
*/
|
|
227
|
-
function nilIdValue(meta) {
|
|
228
|
-
switch (meta.idDbType) {
|
|
229
|
-
case "int":
|
|
230
|
-
case "bigint": return -1;
|
|
231
|
-
case "uuid": return "00000000-0000-0000-0000-000000000000";
|
|
232
|
-
case "text": return "0";
|
|
233
|
-
default: return require_utils.assertNever(meta.idDbType);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
218
|
/** Returns true for values that should be treated like an omitted filter. */
|
|
237
219
|
function shouldPruneValueFilter(key, value) {
|
|
238
220
|
if (value === void 0) return true;
|
|
239
221
|
return value === false && key !== "eq" && key !== "ne";
|
|
240
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Returns whether an entity is known not to be queryable from the database.
|
|
225
|
+
*
|
|
226
|
+
* We check `idTaggedMaybe` instead of `isNewEntity` because commit rules run after INSERTs while those
|
|
227
|
+
* entities still report as new until the flush completes (and yet, because they are in the database, we
|
|
228
|
+
* must keep them in the query instead of ignoring them). An ID can also be assigned before INSERT, so an
|
|
229
|
+
* ID-bearing entity conservatively falls through to a harmless database query.
|
|
230
|
+
*/
|
|
231
|
+
function isNotInDb(value) {
|
|
232
|
+
return value.idTaggedMaybe === void 0;
|
|
233
|
+
}
|
|
241
234
|
//#endregion
|
|
242
|
-
exports.isNilIdValue = isNilIdValue;
|
|
243
235
|
exports.makeLike = makeLike;
|
|
244
236
|
exports.mapToDb = mapToDb;
|
|
245
237
|
exports.parseEntityFilter = parseEntityFilter;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valueFilters.cjs","names":["isAlias","isScope","isEntity","isPlainObject","isDefined","assertNever"],"sources":["../../src/queries/valueFilters.ts"],"sourcesContent":["import { isPlainObject } from \"joist-utils\";\nimport { type Entity, isEntity } from \"src/Entity.ts\";\nimport { isDefined } from \"src/EntityManager.ts\";\nimport type { EntityMetadata } from \"src/EntityMetadata.ts\";\nimport { isAlias } from \"src/queries/find/Aliases.ts\";\nimport type { ValueFilter } from \"src/queries/find/EntityFilter.ts\";\nimport { isScope } from \"src/queries/find/scopes.ts\";\nimport type { ParsedValueFilter } from \"src/queries/parsedConditions.ts\";\nimport type { Column } from \"src/serde/columns.ts\";\nimport { assertNever } from \"src/utils.ts\";\n\n/** An ADT version of `EntityFilter`. */\nexport type ParsedEntityFilter =\n // ParsedValueFilter is any simple match on `id`\n | ParsedValueFilter<string | number>\n // Otherwise we return the join/complex\n | { kind: \"join\"; subFilter: object };\n\n/** Parses an entity filter, which could be \"just an id\", an array of ids, or a nested filter. */\nexport function parseEntityFilter(meta: EntityMetadata, filter: any): ParsedEntityFilter | undefined {\n if (filter === undefined) {\n // This matches legacy `em.find(Book, { author: undefined })` behavior\n return undefined;\n } else if (isAlias(filter)) {\n // We're just binding an alias to this position in the join tree\n return undefined;\n } else if (isScope(filter)) {\n // A scope on a relation (e.g. `{ author: Author.adult }`) always implies conditions on the\n // related entity, so signal \"needs a join\" via the existing `join` discriminant rather than\n // adding a dedicated `kind: \"scope\"` that every relation branch would have to learn. The empty\n // `subFilter` is intentionally never read: the m2o/m2m branches only check `kind === \"join\"` to\n // decide whether to join, then re-dispatch the original scope value back through `addTable`,\n // where `isScope(...)` routes it to `addScopeFilterAt` to apply the scope's actual fragments.\n return { kind: \"join\", subFilter: {} };\n } else if (filter === null) {\n return { kind: \"is-null\" };\n } else if (typeof filter === \"string\" || typeof filter === \"number\") {\n return { kind: \"eq\", value: filter };\n } else if (typeof filter === \"boolean\") {\n return filter ? { kind: \"not-null\" } : { kind: \"is-null\" };\n } else if (Array.isArray(filter)) {\n return {\n kind: \"in\",\n value: filter.map((v: string | number | Entity) => {\n return isEntity(v) ? (v.idTaggedMaybe ?? nilIdValue(meta)) : v;\n }),\n };\n } else if (isEntity(filter)) {\n return { kind: \"eq\", value: filter.idTaggedMaybe || nilIdValue(meta) };\n } else if (typeof filter === \"object\") {\n // Looking for `{ firstName: \"f1\" }` or `{ ne: \"f1\" }`\n const keys = Object.keys(filter);\n // Special case only looking at `ne`\n if (keys.length === 1 && keys[0] === \"ne\") {\n const value = filter[\"ne\"];\n if (value === undefined) {\n return undefined;\n } else if (value === null) {\n return { kind: \"not-null\" };\n } else if (typeof value === \"string\" || typeof value === \"number\") {\n return { kind: \"ne\", value };\n } else if (isEntity(value)) {\n return { kind: \"ne\", value: value.idTaggedMaybe || nilIdValue(meta) };\n } else {\n throw new Error(`Unsupported \"ne\" value ${value}`);\n }\n }\n // Special case only looking at `id`\n if (keys.length === 1 && keys[0] === \"id\") {\n const value = filter[\"id\"];\n if (value === undefined) {\n return undefined;\n } else if (value === null) {\n return { kind: \"is-null\" };\n } else if (typeof value === \"string\" || typeof value === \"number\") {\n return { kind: \"eq\", value };\n } else if (isEntity(value)) {\n return { kind: \"eq\", value: value.idTaggedMaybe || nilIdValue(meta) };\n } else {\n return parseValueFilter(value)[0] as any;\n }\n }\n // Look for subFilter values being EntityFilter-ish instances like ManyToOneReference\n // that have an id, and so structurally match the entity filter without really being filters,\n // and convert them over here before getting into parseValueFilter.\n const subFilter = {} as any;\n for (const [key, value] of Object.entries(filter)) {\n if (value && typeof value === \"object\" && !isPlainObject(value) && \"idTaggedMaybe\" in value) {\n subFilter[key] = value.idTaggedMaybe || nilIdValue(meta);\n } else {\n subFilter[key] = value;\n }\n }\n return { kind: \"join\", subFilter };\n } else {\n throw new Error(`Unrecognized filter ${filter}`);\n }\n}\n\n/**\n * Parses the many/hodgepodge (ergonomic!) patterns of value filters into a `ParsedValueFilter[]`.\n *\n * Note we return an array because filter might be a `ValueGraphQLFilter` that is allowed to have\n * multiple conditions, i.e. `{ lt: 10, gt: 5 }`.\n */\nexport function parseValueFilter<V>(filter: ValueFilter<V, any>): ParsedValueFilter<V>[] {\n if (filter === null) {\n return [{ kind: \"is-null\" }];\n } else if (filter === undefined) {\n // This is legacy behavior where `em.find(Book, { author: undefined })` would match all books\n return [];\n } else if (Array.isArray(filter)) {\n return [{ kind: \"in\", value: filter }];\n } else if (isPlainObject(filter)) {\n const keys = Object.keys(filter);\n if (keys.length === 0) {\n // Should this be an error?\n return [];\n } else if (keys.length === 2 && \"op\" in filter && \"value\" in filter) {\n // Probe for `findGql` op & value\n const { op, value } = filter;\n if (shouldPruneValueFilter(op, value)) {\n return [];\n } else if (value === null) {\n return [{ kind: \"is-null\" }];\n } else {\n return [{ kind: op, value: value ?? null }];\n }\n } else if (keys.length === 2 && \"gte\" in filter && \"lte\" in filter) {\n const { gte, lte } = filter;\n return [{ kind: \"between\", value: [gte, lte] }];\n } else {\n return Object.entries(filter)\n .map(([key, value]) => {\n // Always do condition pruning on the value\n if (shouldPruneValueFilter(key, value)) {\n return undefined;\n }\n switch (key) {\n case \"eq\":\n if (value === null) {\n return { kind: \"is-null\" as const };\n } else {\n return { kind: \"eq\" as const, value: filter[key] };\n }\n case \"ne\":\n if (value === null) {\n return { kind: \"not-null\" as const };\n } else {\n return { kind: \"ne\" as const, value: filter[key] ?? null };\n }\n case \"in\":\n case \"nin\":\n case \"gt\":\n case \"gte\":\n case \"lt\":\n case \"lte\":\n case \"like\":\n case \"nlike\":\n case \"ilike\":\n case \"nilike\":\n case \"regex\":\n case \"nregex\":\n case \"iregex\":\n case \"niregex\":\n case \"contains\":\n case \"overlaps\":\n case \"containedBy\":\n return { kind: key, value: filter[key] };\n case \"pathExists\":\n return { kind: \"jsonPathExists\" as const, value: filter[key] };\n case \"pathIsTrue\":\n return { kind: \"jsonPathPredicate\" as const, value: filter[key] };\n case \"search\":\n return { kind: \"ilike\" as const, value: makeLike(filter[key]) };\n case \"between\":\n return { kind: key, value: filter[key] };\n default:\n throw new Error(`Unsupported value filter key ${key}`);\n }\n })\n .filter(isDefined);\n }\n } else {\n // This is a primitive like a string, number\n return [{ kind: \"eq\", value: filter }];\n }\n}\n\n/** Converts domain-level values like string ids/enums into their db equivalent. */\nexport function mapToDb(column: Column, filter: ParsedValueFilter<any>): ParsedValueFilter<any> {\n // ...to teach this `mapToDb` function to handle/rewrite `in: [1, null]` handling, we'd need to:\n // 1. return a maybe-simple/maybe-nested condition, so basically a `ParsedExpressionCondition`, because\n // this would let `in` return an `{ or: ... }` to all the callers.\n // 2. also return `{ parsed: ParsedExpressionCondition, simples: SimpleCondition[] }` tuple, for the\n // alias `addCondition` processing to track the `simples` and rewrite their alias when later bound.\n switch (filter.kind) {\n case \"eq\":\n case \"gt\":\n case \"gte\":\n case \"ne\":\n case \"lt\":\n case \"lte\":\n case \"like\":\n case \"nlike\":\n case \"ilike\":\n case \"nilike\":\n case \"regex\":\n case \"nregex\":\n case \"iregex\":\n case \"niregex\":\n filter.value = column.mapToDb(filter.value);\n return filter;\n case \"in\":\n if (column.isArray) {\n // Arrays need a special operator\n return { kind: \"contains\", value: column.mapToDb(filter.value) };\n } else {\n filter.value = filter.value.map((v) => column.mapToDb(v));\n }\n return filter;\n case \"nin\":\n if (column.isArray) {\n // Arrays need a special operator\n throw new Error(\"The nin operator is not supported on array columns yet\");\n } else {\n filter.value = filter.value.map((v) => column.mapToDb(v));\n }\n return filter;\n case \"contains\":\n case \"ncontains\":\n case \"overlaps\":\n case \"noverlaps\":\n case \"containedBy\":\n const supportsContains = column.isArray || column.dbType === \"jsonb\";\n if (!supportsContains) {\n throw new Error(`${filter.kind} is only unsupported on array or jsonb columns`);\n }\n if (column.isArray) {\n filter.value = column.mapToDb(filter.value);\n } else {\n // leave jsonb values alone\n }\n return filter;\n case \"between\":\n filter.value = [column.mapToDb(filter.value[0]), column.mapToDb(filter.value[1])];\n return filter;\n case \"is-null\":\n case \"not-null\":\n case \"jsonPathExists\":\n case \"jsonPathPredicate\":\n return filter;\n default:\n throw assertNever(filter);\n }\n}\n\n/** Converts a search term like `foo bar` into a SQL `like` pattern like `%foo%bar%`. */\nexport function makeLike(search: any | undefined): any {\n return search ? `%${search.replace(/\\s+/g, \"%\")}%` : undefined;\n}\n\n/** Recognizes placeholder IDs used when a filter contains an unpersisted entity. */\nexport function isNilIdValue(value: unknown): boolean {\n return value === -1 || value === \"00000000-0000-0000-0000-000000000000\";\n}\n\n/**\n * We use this value if users include new (id-less) entities as em.find conditions.\n *\n * The idea is that this condition would never be met, but we still want to do the em.find\n * query in case it's in an `OR` clause that would match false, but some other part of the\n * clause would match. I.e. instead of just skipping the DB query all together, which is\n * also something we could consider doing.\n *\n * For int IDs we use -1, and for uuid IDs, we use the nil UUID value:\n *\n * https://en.wikipedia.org/wiki/Universally_unique_identifier#Nil_UUID\n */\nfunction nilIdValue(meta: EntityMetadata): any {\n switch (meta.idDbType) {\n case \"int\":\n case \"bigint\":\n return -1;\n case \"uuid\":\n return \"00000000-0000-0000-0000-000000000000\";\n case \"text\":\n return \"0\";\n default:\n return assertNever(meta.idDbType);\n }\n}\n\n/** Returns true for values that should be treated like an omitted filter. */\nfunction shouldPruneValueFilter(key: unknown, value: unknown): boolean {\n if (value === undefined) return true;\n return value === false && key !== \"eq\" && key !== \"ne\";\n}\n"],"mappings":";;;;;;;;;AAmBA,SAAgB,kBAAkB,MAAsB,QAA6C;CACnG,IAAI,WAAW,KAAA,GAEb;MACK,IAAIA,6BAAAA,QAAQ,MAAM,GAEvB;MACK,IAAIC,4BAAAA,QAAQ,MAAM,GAOvB,OAAO;EAAE,MAAM;EAAQ,WAAW,CAAC;CAAE;MAChC,IAAI,WAAW,MACpB,OAAO,EAAE,MAAM,UAAU;MACpB,IAAI,OAAO,WAAW,YAAY,OAAO,WAAW,UACzD,OAAO;EAAE,MAAM;EAAM,OAAO;CAAO;MAC9B,IAAI,OAAO,WAAW,WAC3B,OAAO,SAAS,EAAE,MAAM,WAAW,IAAI,EAAE,MAAM,UAAU;MACpD,IAAI,MAAM,QAAQ,MAAM,GAC7B,OAAO;EACL,MAAM;EACN,OAAO,OAAO,KAAK,MAAgC;GACjD,OAAOC,eAAAA,SAAS,CAAC,IAAK,EAAE,iBAAiB,WAAW,IAAI,IAAK;EAC/D,CAAC;CACH;MACK,IAAIA,eAAAA,SAAS,MAAM,GACxB,OAAO;EAAE,MAAM;EAAM,OAAO,OAAO,iBAAiB,WAAW,IAAI;CAAE;MAChE,IAAI,OAAO,WAAW,UAAU;EAErC,MAAM,OAAO,OAAO,KAAK,MAAM;EAE/B,IAAI,KAAK,WAAW,KAAK,KAAK,OAAO,MAAM;GACzC,MAAM,QAAQ,OAAO;GACrB,IAAI,UAAU,KAAA,GACZ;QACK,IAAI,UAAU,MACnB,OAAO,EAAE,MAAM,WAAW;QACrB,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UACvD,OAAO;IAAE,MAAM;IAAM;GAAM;QACtB,IAAIA,eAAAA,SAAS,KAAK,GACvB,OAAO;IAAE,MAAM;IAAM,OAAO,MAAM,iBAAiB,WAAW,IAAI;GAAE;QAEpE,MAAM,IAAI,MAAM,0BAA0B,OAAO;EAErD;EAEA,IAAI,KAAK,WAAW,KAAK,KAAK,OAAO,MAAM;GACzC,MAAM,QAAQ,OAAO;GACrB,IAAI,UAAU,KAAA,GACZ;QACK,IAAI,UAAU,MACnB,OAAO,EAAE,MAAM,UAAU;QACpB,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UACvD,OAAO;IAAE,MAAM;IAAM;GAAM;QACtB,IAAIA,eAAAA,SAAS,KAAK,GACvB,OAAO;IAAE,MAAM;IAAM,OAAO,MAAM,iBAAiB,WAAW,IAAI;GAAE;QAEpE,OAAO,iBAAiB,KAAK,CAAC,CAAC;EAEnC;EAIA,MAAM,YAAY,CAAC;EACnB,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC9C,IAAI,SAAS,OAAO,UAAU,YAAY,EAAA,GAACC,YAAAA,cAAAA,CAAc,KAAK,KAAK,mBAAmB,OACpF,UAAU,OAAO,MAAM,iBAAiB,WAAW,IAAI;OAEvD,UAAU,OAAO;EAGrB,OAAO;GAAE,MAAM;GAAQ;EAAU;CACnC,OACE,MAAM,IAAI,MAAM,uBAAuB,QAAQ;AAEnD;;;;;;;AAQA,SAAgB,iBAAoB,QAAqD;CACvF,IAAI,WAAW,MACb,OAAO,CAAC,EAAE,MAAM,UAAU,CAAC;MACtB,IAAI,WAAW,KAAA,GAEpB,OAAO,CAAC;MACH,IAAI,MAAM,QAAQ,MAAM,GAC7B,OAAO,CAAC;EAAE,MAAM;EAAM,OAAO;CAAO,CAAC;MAChC,KAAA,GAAIA,YAAAA,cAAAA,CAAc,MAAM,GAAG;EAChC,MAAM,OAAO,OAAO,KAAK,MAAM;EAC/B,IAAI,KAAK,WAAW,GAElB,OAAO,CAAC;OACH,IAAI,KAAK,WAAW,KAAK,QAAQ,UAAU,WAAW,QAAQ;GAEnE,MAAM,EAAE,IAAI,UAAU;GACtB,IAAI,uBAAuB,IAAI,KAAK,GAClC,OAAO,CAAC;QACH,IAAI,UAAU,MACnB,OAAO,CAAC,EAAE,MAAM,UAAU,CAAC;QAE3B,OAAO,CAAC;IAAE,MAAM;IAAI,OAAO,SAAS;GAAK,CAAC;EAE9C,OAAO,IAAI,KAAK,WAAW,KAAK,SAAS,UAAU,SAAS,QAAQ;GAClE,MAAM,EAAE,KAAK,QAAQ;GACrB,OAAO,CAAC;IAAE,MAAM;IAAW,OAAO,CAAC,KAAK,GAAG;GAAE,CAAC;EAChD,OACE,OAAO,OAAO,QAAQ,MAAM,CAAC,CAC1B,KAAK,CAAC,KAAK,WAAW;GAErB,IAAI,uBAAuB,KAAK,KAAK,GACnC;GAEF,QAAQ,KAAR;IACE,KAAK,MACH,IAAI,UAAU,MACZ,OAAO,EAAE,MAAM,UAAmB;SAElC,OAAO;KAAE,MAAM;KAAe,OAAO,OAAO;IAAK;IAErD,KAAK,MACH,IAAI,UAAU,MACZ,OAAO,EAAE,MAAM,WAAoB;SAEnC,OAAO;KAAE,MAAM;KAAe,OAAO,OAAO,QAAQ;IAAK;IAE7D,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK,eACH,OAAO;KAAE,MAAM;KAAK,OAAO,OAAO;IAAK;IACzC,KAAK,cACH,OAAO;KAAE,MAAM;KAA2B,OAAO,OAAO;IAAK;IAC/D,KAAK,cACH,OAAO;KAAE,MAAM;KAA8B,OAAO,OAAO;IAAK;IAClE,KAAK,UACH,OAAO;KAAE,MAAM;KAAkB,OAAO,SAAS,OAAO,IAAI;IAAE;IAChE,KAAK,WACH,OAAO;KAAE,MAAM;KAAK,OAAO,OAAO;IAAK;IACzC,SACE,MAAM,IAAI,MAAM,gCAAgC,KAAK;GACzD;EACF,CAAC,CAAC,CACD,OAAOC,sBAAAA,SAAS;CAEvB,OAEE,OAAO,CAAC;EAAE,MAAM;EAAM,OAAO;CAAO,CAAC;AAEzC;;AAGA,SAAgB,QAAQ,QAAgB,QAAwD;CAM9F,QAAQ,OAAO,MAAf;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;GACH,OAAO,QAAQ,OAAO,QAAQ,OAAO,KAAK;GAC1C,OAAO;EACT,KAAK;GACH,IAAI,OAAO,SAET,OAAO;IAAE,MAAM;IAAY,OAAO,OAAO,QAAQ,OAAO,KAAK;GAAE;QAE/D,OAAO,QAAQ,OAAO,MAAM,KAAK,MAAM,OAAO,QAAQ,CAAC,CAAC;GAE1D,OAAO;EACT,KAAK;GACH,IAAI,OAAO,SAET,MAAM,IAAI,MAAM,wDAAwD;QAExE,OAAO,QAAQ,OAAO,MAAM,KAAK,MAAM,OAAO,QAAQ,CAAC,CAAC;GAE1D,OAAO;EACT,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;GAEH,IAAI,EADqB,OAAO,WAAW,OAAO,WAAW,UAE3D,MAAM,IAAI,MAAM,GAAG,OAAO,KAAK,+CAA+C;GAEhF,IAAI,OAAO,SACT,OAAO,QAAQ,OAAO,QAAQ,OAAO,KAAK;GAI5C,OAAO;EACT,KAAK;GACH,OAAO,QAAQ,CAAC,OAAO,QAAQ,OAAO,MAAM,EAAE,GAAG,OAAO,QAAQ,OAAO,MAAM,EAAE,CAAC;GAChF,OAAO;EACT,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,qBACH,OAAO;EACT,SACE,MAAMC,cAAAA,YAAY,MAAM;CAC5B;AACF;;AAGA,SAAgB,SAAS,QAA8B;CACrD,OAAO,SAAS,IAAI,OAAO,QAAQ,QAAQ,GAAG,EAAE,KAAK,KAAA;AACvD;;AAGA,SAAgB,aAAa,OAAyB;CACpD,OAAO,UAAU,MAAM,UAAU;AACnC;;;;;;;;;;;;;AAcA,SAAS,WAAW,MAA2B;CAC7C,QAAQ,KAAK,UAAb;EACE,KAAK;EACL,KAAK,UACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,SACE,OAAOA,cAAAA,YAAY,KAAK,QAAQ;CACpC;AACF;;AAGA,SAAS,uBAAuB,KAAc,OAAyB;CACrE,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,OAAO,UAAU,SAAS,QAAQ,QAAQ,QAAQ;AACpD"}
|
|
1
|
+
{"version":3,"file":"valueFilters.cjs","names":["isAlias","isScope","isEntity","isPlainObject","isDefined","assertNever"],"sources":["../../src/queries/valueFilters.ts"],"sourcesContent":["import { isPlainObject } from \"joist-utils\";\nimport { type Entity, isEntity } from \"src/Entity.ts\";\nimport { isDefined } from \"src/EntityManager.ts\";\nimport type { EntityMetadata } from \"src/EntityMetadata.ts\";\nimport { isAlias } from \"src/queries/find/Aliases.ts\";\nimport type { ValueFilter } from \"src/queries/find/EntityFilter.ts\";\nimport { isScope } from \"src/queries/find/scopes.ts\";\nimport type { ParsedValueFilter } from \"src/queries/parsedConditions.ts\";\nimport type { Column } from \"src/serde/columns.ts\";\nimport { assertNever } from \"src/utils.ts\";\n\n/** An ADT version of `EntityFilter`. */\nexport type ParsedEntityFilter =\n // ParsedValueFilter is any simple match on `id`\n | ParsedValueFilter<string | number>\n // Otherwise we return the join/complex\n | { kind: \"join\"; subFilter: object };\n\n/** Parses an entity filter, which could be \"just an id\", an array of ids, or a nested filter. */\nexport function parseEntityFilter(_meta: EntityMetadata, filter: any): ParsedEntityFilter | undefined {\n if (filter === undefined) {\n // This matches legacy `em.find(Book, { author: undefined })` behavior\n return undefined;\n } else if (isAlias(filter)) {\n // We're just binding an alias to this position in the join tree\n return undefined;\n } else if (isScope(filter)) {\n // A scope on a relation (e.g. `{ author: Author.adult }`) always implies conditions on the\n // related entity, so signal \"needs a join\" via the existing `join` discriminant rather than\n // adding a dedicated `kind: \"scope\"` that every relation branch would have to learn. The empty\n // `subFilter` is intentionally never read: the m2o/m2m branches only check `kind === \"join\"` to\n // decide whether to join, then re-dispatch the original scope value back through `addTable`,\n // where `isScope(...)` routes it to `addScopeFilterAt` to apply the scope's actual fragments.\n return { kind: \"join\", subFilter: {} };\n } else if (filter === null) {\n return { kind: \"is-null\" };\n } else if (typeof filter === \"string\" || typeof filter === \"number\") {\n return { kind: \"eq\", value: filter };\n } else if (typeof filter === \"boolean\") {\n return filter ? { kind: \"not-null\" } : { kind: \"is-null\" };\n } else if (Array.isArray(filter)) {\n return {\n kind: \"in\",\n value: filter.flatMap((value: string | number | Entity) => {\n if (!isEntity(value)) return [value];\n // If no IDs remain, `in: []` marks the condition as unable to match a database row.\n return isNotInDb(value) ? [] : [value.idTagged];\n }),\n };\n } else if (isEntity(filter)) {\n // We use `in: []` to mark an ID-less entity as unable to match a database row.\n return isNotInDb(filter) ? { kind: \"in\", value: [] } : { kind: \"eq\", value: filter.idTagged };\n } else if (typeof filter === \"object\") {\n // Looking for `{ firstName: \"f1\" }` or `{ ne: \"f1\" }`\n const keys = Object.keys(filter);\n // Special case only looking at `ne`\n if (keys.length === 1 && keys[0] === \"ne\") {\n const value = filter[\"ne\"];\n if (value === undefined) {\n return undefined;\n } else if (value === null) {\n return { kind: \"not-null\" };\n } else if (typeof value === \"string\" || typeof value === \"number\") {\n return { kind: \"ne\", value };\n } else if (isEntity(value)) {\n // No persisted FK can equal an ID-less entity, and SQL `!=` excludes nulls, so this is `IS NOT NULL`.\n return isNotInDb(value) ? { kind: \"not-null\" } : { kind: \"ne\", value: value.idTagged };\n } else {\n throw new Error(`Unsupported \"ne\" value ${value}`);\n }\n }\n // Special case only looking at `id`\n if (keys.length === 1 && keys[0] === \"id\") {\n const value = filter[\"id\"];\n if (value === undefined) {\n return undefined;\n } else if (value === null) {\n return { kind: \"is-null\" };\n } else if (typeof value === \"string\" || typeof value === \"number\") {\n return { kind: \"eq\", value };\n } else if (isEntity(value)) {\n return isNotInDb(value) ? { kind: \"in\", value: [] } : { kind: \"eq\", value: value.idTagged };\n } else {\n return parseValueFilter(value)[0] as any;\n }\n }\n // Look for subFilter values being EntityFilter-ish instances like ManyToOneReference\n // that have an id, and so structurally match the entity filter without really being filters,\n // and convert them over here before getting into parseValueFilter.\n const subFilter = {} as any;\n for (const [key, value] of Object.entries(filter)) {\n if (value && typeof value === \"object\" && !isEntity(value) && !isPlainObject(value) && \"idTaggedMaybe\" in value) {\n subFilter[key] = value.idTaggedMaybe === undefined ? [] : value.idTaggedMaybe;\n } else {\n subFilter[key] = value;\n }\n }\n return { kind: \"join\", subFilter };\n } else {\n throw new Error(`Unrecognized filter ${filter}`);\n }\n}\n\n/**\n * Parses the many/hodgepodge (ergonomic!) patterns of value filters into a `ParsedValueFilter[]`.\n *\n * Note we return an array because filter might be a `ValueGraphQLFilter` that is allowed to have\n * multiple conditions, i.e. `{ lt: 10, gt: 5 }`.\n */\nexport function parseValueFilter<V>(filter: ValueFilter<V, any>): ParsedValueFilter<V>[] {\n if (filter === null) {\n return [{ kind: \"is-null\" }];\n } else if (filter === undefined) {\n // This is legacy behavior where `em.find(Book, { author: undefined })` would match all books\n return [];\n } else if (Array.isArray(filter)) {\n return [{ kind: \"in\", value: filter }];\n } else if (isPlainObject(filter)) {\n const keys = Object.keys(filter);\n if (keys.length === 0) {\n // Should this be an error?\n return [];\n } else if (keys.length === 2 && \"op\" in filter && \"value\" in filter) {\n // Probe for `findGql` op & value\n const { op, value } = filter;\n if (shouldPruneValueFilter(op, value)) {\n return [];\n } else if (value === null) {\n return [{ kind: \"is-null\" }];\n } else {\n return [{ kind: op, value: value ?? null }];\n }\n } else if (keys.length === 2 && \"gte\" in filter && \"lte\" in filter) {\n const { gte, lte } = filter;\n return [{ kind: \"between\", value: [gte, lte] }];\n } else {\n return Object.entries(filter)\n .map(([key, value]) => {\n // Always do condition pruning on the value\n if (shouldPruneValueFilter(key, value)) {\n return undefined;\n }\n switch (key) {\n case \"eq\":\n if (value === null) {\n return { kind: \"is-null\" as const };\n } else {\n return { kind: \"eq\" as const, value: filter[key] };\n }\n case \"ne\":\n if (value === null) {\n return { kind: \"not-null\" as const };\n } else {\n return { kind: \"ne\" as const, value: filter[key] ?? null };\n }\n case \"in\":\n case \"nin\":\n case \"gt\":\n case \"gte\":\n case \"lt\":\n case \"lte\":\n case \"like\":\n case \"nlike\":\n case \"ilike\":\n case \"nilike\":\n case \"regex\":\n case \"nregex\":\n case \"iregex\":\n case \"niregex\":\n case \"contains\":\n case \"overlaps\":\n case \"containedBy\":\n return { kind: key, value: filter[key] };\n case \"pathExists\":\n return { kind: \"jsonPathExists\" as const, value: filter[key] };\n case \"pathIsTrue\":\n return { kind: \"jsonPathPredicate\" as const, value: filter[key] };\n case \"search\":\n return { kind: \"ilike\" as const, value: makeLike(filter[key]) };\n case \"between\":\n return { kind: key, value: filter[key] };\n default:\n throw new Error(`Unsupported value filter key ${key}`);\n }\n })\n .filter(isDefined);\n }\n } else {\n // This is a primitive like a string, number\n return [{ kind: \"eq\", value: filter }];\n }\n}\n\n/** Converts domain-level values like string ids/enums into their db equivalent. */\nexport function mapToDb(column: Column, filter: ParsedValueFilter<any>): ParsedValueFilter<any> {\n // ...to teach this `mapToDb` function to handle/rewrite `in: [1, null]` handling, we'd need to:\n // 1. return a maybe-simple/maybe-nested condition, so basically a `ParsedExpressionCondition`, because\n // this would let `in` return an `{ or: ... }` to all the callers.\n // 2. also return `{ parsed: ParsedExpressionCondition, simples: SimpleCondition[] }` tuple, for the\n // alias `addCondition` processing to track the `simples` and rewrite their alias when later bound.\n switch (filter.kind) {\n case \"eq\":\n case \"gt\":\n case \"gte\":\n case \"ne\":\n case \"lt\":\n case \"lte\":\n case \"like\":\n case \"nlike\":\n case \"ilike\":\n case \"nilike\":\n case \"regex\":\n case \"nregex\":\n case \"iregex\":\n case \"niregex\":\n filter.value = column.mapToDb(filter.value);\n return filter;\n case \"in\":\n if (column.isArray) {\n // Arrays need a special operator\n return { kind: \"contains\", value: column.mapToDb(filter.value) };\n } else {\n filter.value = filter.value.map((v) => column.mapToDb(v));\n }\n return filter;\n case \"nin\":\n if (column.isArray) {\n // Arrays need a special operator\n throw new Error(\"The nin operator is not supported on array columns yet\");\n } else {\n filter.value = filter.value.map((v) => column.mapToDb(v));\n }\n return filter;\n case \"contains\":\n case \"ncontains\":\n case \"overlaps\":\n case \"noverlaps\":\n case \"containedBy\":\n const supportsContains = column.isArray || column.dbType === \"jsonb\";\n if (!supportsContains) {\n throw new Error(`${filter.kind} is only unsupported on array or jsonb columns`);\n }\n if (column.isArray) {\n filter.value = column.mapToDb(filter.value);\n } else {\n // leave jsonb values alone\n }\n return filter;\n case \"between\":\n filter.value = [column.mapToDb(filter.value[0]), column.mapToDb(filter.value[1])];\n return filter;\n case \"is-null\":\n case \"not-null\":\n case \"jsonPathExists\":\n case \"jsonPathPredicate\":\n return filter;\n default:\n throw assertNever(filter);\n }\n}\n\n/** Converts a search term like `foo bar` into a SQL `like` pattern like `%foo%bar%`. */\nexport function makeLike(search: any | undefined): any {\n return search ? `%${search.replace(/\\s+/g, \"%\")}%` : undefined;\n}\n\n/** Returns true for values that should be treated like an omitted filter. */\nfunction shouldPruneValueFilter(key: unknown, value: unknown): boolean {\n if (value === undefined) return true;\n return value === false && key !== \"eq\" && key !== \"ne\";\n}\n\n/**\n * Returns whether an entity is known not to be queryable from the database.\n *\n * We check `idTaggedMaybe` instead of `isNewEntity` because commit rules run after INSERTs while those\n * entities still report as new until the flush completes (and yet, because they are in the database, we\n * must keep them in the query instead of ignoring them). An ID can also be assigned before INSERT, so an\n * ID-bearing entity conservatively falls through to a harmless database query.\n */\nfunction isNotInDb(value: Entity): boolean {\n return value.idTaggedMaybe === undefined;\n}\n"],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ type ParsedEntityFilter = ParsedValueFilter<string | number> | {
|
|
|
9
9
|
subFilter: object;
|
|
10
10
|
};
|
|
11
11
|
/** Parses an entity filter, which could be "just an id", an array of ids, or a nested filter. */
|
|
12
|
-
declare function parseEntityFilter(
|
|
12
|
+
declare function parseEntityFilter(_meta: EntityMetadata, filter: any): ParsedEntityFilter | undefined;
|
|
13
13
|
/**
|
|
14
14
|
* Parses the many/hodgepodge (ergonomic!) patterns of value filters into a `ParsedValueFilter[]`.
|
|
15
15
|
*
|
|
@@ -21,8 +21,6 @@ declare function parseValueFilter<V>(filter: ValueFilter<V, any>): ParsedValueFi
|
|
|
21
21
|
declare function mapToDb(column: Column, filter: ParsedValueFilter<any>): ParsedValueFilter<any>;
|
|
22
22
|
/** Converts a search term like `foo bar` into a SQL `like` pattern like `%foo%bar%`. */
|
|
23
23
|
declare function makeLike(search: any | undefined): any;
|
|
24
|
-
/** Recognizes placeholder IDs used when a filter contains an unpersisted entity. */
|
|
25
|
-
declare function isNilIdValue(value: unknown): boolean;
|
|
26
24
|
//#endregion
|
|
27
|
-
export { ParsedEntityFilter,
|
|
25
|
+
export { ParsedEntityFilter, makeLike, mapToDb, parseEntityFilter, parseValueFilter };
|
|
28
26
|
//# sourceMappingURL=valueFilters.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valueFilters.d.cts","names":[],"sources":["../../src/queries/valueFilters.ts"],"mappings":";;;;;;KAYY,qBAER;EAEE;EAAc;;;iBAGJ,kBAAkB,
|
|
1
|
+
{"version":3,"file":"valueFilters.d.cts","names":[],"sources":["../../src/queries/valueFilters.ts"],"mappings":";;;;;;KAYY,qBAER;EAEE;EAAc;;;iBAGJ,kBAAkB,OAAO,gBAAgB,cAAc;;;;;;;iBA0FvD,iBAAiB,GAAG,QAAQ,YAAY,UAAU,kBAAkB;;iBAqFpE,QAAQ,QAAQ,QAAQ,QAAQ,yBAAyB;;iBAoEzD,SAAS"}
|
|
@@ -9,7 +9,7 @@ type ParsedEntityFilter = ParsedValueFilter<string | number> | {
|
|
|
9
9
|
subFilter: object;
|
|
10
10
|
};
|
|
11
11
|
/** Parses an entity filter, which could be "just an id", an array of ids, or a nested filter. */
|
|
12
|
-
declare function parseEntityFilter(
|
|
12
|
+
declare function parseEntityFilter(_meta: EntityMetadata, filter: any): ParsedEntityFilter | undefined;
|
|
13
13
|
/**
|
|
14
14
|
* Parses the many/hodgepodge (ergonomic!) patterns of value filters into a `ParsedValueFilter[]`.
|
|
15
15
|
*
|
|
@@ -21,8 +21,6 @@ declare function parseValueFilter<V>(filter: ValueFilter<V, any>): ParsedValueFi
|
|
|
21
21
|
declare function mapToDb(column: Column, filter: ParsedValueFilter<any>): ParsedValueFilter<any>;
|
|
22
22
|
/** Converts a search term like `foo bar` into a SQL `like` pattern like `%foo%bar%`. */
|
|
23
23
|
declare function makeLike(search: any | undefined): any;
|
|
24
|
-
/** Recognizes placeholder IDs used when a filter contains an unpersisted entity. */
|
|
25
|
-
declare function isNilIdValue(value: unknown): boolean;
|
|
26
24
|
//#endregion
|
|
27
|
-
export { ParsedEntityFilter,
|
|
25
|
+
export { ParsedEntityFilter, makeLike, mapToDb, parseEntityFilter, parseValueFilter };
|
|
28
26
|
//# sourceMappingURL=valueFilters.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valueFilters.d.mts","names":[],"sources":["../../src/queries/valueFilters.ts"],"mappings":";;;;;;KAYY,qBAER;EAEE;EAAc;;;iBAGJ,kBAAkB,
|
|
1
|
+
{"version":3,"file":"valueFilters.d.mts","names":[],"sources":["../../src/queries/valueFilters.ts"],"mappings":";;;;;;KAYY,qBAER;EAEE;EAAc;;;iBAGJ,kBAAkB,OAAO,gBAAgB,cAAc;;;;;;;iBA0FvD,iBAAiB,GAAG,QAAQ,YAAY,UAAU,kBAAkB;;iBAqFpE,QAAQ,QAAQ,QAAQ,QAAQ,yBAAyB;;iBAoEzD,SAAS"}
|
|
@@ -6,7 +6,7 @@ import { isEntity } from "../Entity.js";
|
|
|
6
6
|
import { isPlainObject } from "joist-utils";
|
|
7
7
|
//#region src/queries/valueFilters.ts
|
|
8
8
|
/** Parses an entity filter, which could be "just an id", an array of ids, or a nested filter. */
|
|
9
|
-
function parseEntityFilter(
|
|
9
|
+
function parseEntityFilter(_meta, filter) {
|
|
10
10
|
if (filter === void 0) return;
|
|
11
11
|
else if (isAlias(filter)) return;
|
|
12
12
|
else if (isScope(filter)) return {
|
|
@@ -21,13 +21,17 @@ function parseEntityFilter(meta, filter) {
|
|
|
21
21
|
else if (typeof filter === "boolean") return filter ? { kind: "not-null" } : { kind: "is-null" };
|
|
22
22
|
else if (Array.isArray(filter)) return {
|
|
23
23
|
kind: "in",
|
|
24
|
-
value: filter.
|
|
25
|
-
|
|
24
|
+
value: filter.flatMap((value) => {
|
|
25
|
+
if (!isEntity(value)) return [value];
|
|
26
|
+
return isNotInDb(value) ? [] : [value.idTagged];
|
|
26
27
|
})
|
|
27
28
|
};
|
|
28
|
-
else if (isEntity(filter)) return {
|
|
29
|
+
else if (isEntity(filter)) return isNotInDb(filter) ? {
|
|
30
|
+
kind: "in",
|
|
31
|
+
value: []
|
|
32
|
+
} : {
|
|
29
33
|
kind: "eq",
|
|
30
|
-
value: filter.
|
|
34
|
+
value: filter.idTagged
|
|
31
35
|
};
|
|
32
36
|
else if (typeof filter === "object") {
|
|
33
37
|
const keys = Object.keys(filter);
|
|
@@ -39,9 +43,9 @@ function parseEntityFilter(meta, filter) {
|
|
|
39
43
|
kind: "ne",
|
|
40
44
|
value
|
|
41
45
|
};
|
|
42
|
-
else if (isEntity(value)) return {
|
|
46
|
+
else if (isEntity(value)) return isNotInDb(value) ? { kind: "not-null" } : {
|
|
43
47
|
kind: "ne",
|
|
44
|
-
value: value.
|
|
48
|
+
value: value.idTagged
|
|
45
49
|
};
|
|
46
50
|
else throw new Error(`Unsupported "ne" value ${value}`);
|
|
47
51
|
}
|
|
@@ -53,14 +57,17 @@ function parseEntityFilter(meta, filter) {
|
|
|
53
57
|
kind: "eq",
|
|
54
58
|
value
|
|
55
59
|
};
|
|
56
|
-
else if (isEntity(value)) return {
|
|
60
|
+
else if (isEntity(value)) return isNotInDb(value) ? {
|
|
61
|
+
kind: "in",
|
|
62
|
+
value: []
|
|
63
|
+
} : {
|
|
57
64
|
kind: "eq",
|
|
58
|
-
value: value.
|
|
65
|
+
value: value.idTagged
|
|
59
66
|
};
|
|
60
67
|
else return parseValueFilter(value)[0];
|
|
61
68
|
}
|
|
62
69
|
const subFilter = {};
|
|
63
|
-
for (const [key, value] of Object.entries(filter)) if (value && typeof value === "object" && !isPlainObject(value) && "idTaggedMaybe" in value) subFilter[key] = value.idTaggedMaybe
|
|
70
|
+
for (const [key, value] of Object.entries(filter)) if (value && typeof value === "object" && !isEntity(value) && !isPlainObject(value) && "idTaggedMaybe" in value) subFilter[key] = value.idTaggedMaybe === void 0 ? [] : value.idTaggedMaybe;
|
|
64
71
|
else subFilter[key] = value;
|
|
65
72
|
return {
|
|
66
73
|
kind: "join",
|
|
@@ -207,37 +214,23 @@ function mapToDb(column, filter) {
|
|
|
207
214
|
function makeLike(search) {
|
|
208
215
|
return search ? `%${search.replace(/\s+/g, "%")}%` : void 0;
|
|
209
216
|
}
|
|
210
|
-
/** Recognizes placeholder IDs used when a filter contains an unpersisted entity. */
|
|
211
|
-
function isNilIdValue(value) {
|
|
212
|
-
return value === -1 || value === "00000000-0000-0000-0000-000000000000";
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* We use this value if users include new (id-less) entities as em.find conditions.
|
|
216
|
-
*
|
|
217
|
-
* The idea is that this condition would never be met, but we still want to do the em.find
|
|
218
|
-
* query in case it's in an `OR` clause that would match false, but some other part of the
|
|
219
|
-
* clause would match. I.e. instead of just skipping the DB query all together, which is
|
|
220
|
-
* also something we could consider doing.
|
|
221
|
-
*
|
|
222
|
-
* For int IDs we use -1, and for uuid IDs, we use the nil UUID value:
|
|
223
|
-
*
|
|
224
|
-
* https://en.wikipedia.org/wiki/Universally_unique_identifier#Nil_UUID
|
|
225
|
-
*/
|
|
226
|
-
function nilIdValue(meta) {
|
|
227
|
-
switch (meta.idDbType) {
|
|
228
|
-
case "int":
|
|
229
|
-
case "bigint": return -1;
|
|
230
|
-
case "uuid": return "00000000-0000-0000-0000-000000000000";
|
|
231
|
-
case "text": return "0";
|
|
232
|
-
default: return assertNever(meta.idDbType);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
217
|
/** Returns true for values that should be treated like an omitted filter. */
|
|
236
218
|
function shouldPruneValueFilter(key, value) {
|
|
237
219
|
if (value === void 0) return true;
|
|
238
220
|
return value === false && key !== "eq" && key !== "ne";
|
|
239
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Returns whether an entity is known not to be queryable from the database.
|
|
224
|
+
*
|
|
225
|
+
* We check `idTaggedMaybe` instead of `isNewEntity` because commit rules run after INSERTs while those
|
|
226
|
+
* entities still report as new until the flush completes (and yet, because they are in the database, we
|
|
227
|
+
* must keep them in the query instead of ignoring them). An ID can also be assigned before INSERT, so an
|
|
228
|
+
* ID-bearing entity conservatively falls through to a harmless database query.
|
|
229
|
+
*/
|
|
230
|
+
function isNotInDb(value) {
|
|
231
|
+
return value.idTaggedMaybe === void 0;
|
|
232
|
+
}
|
|
240
233
|
//#endregion
|
|
241
|
-
export {
|
|
234
|
+
export { makeLike, mapToDb, parseEntityFilter, parseValueFilter };
|
|
242
235
|
|
|
243
236
|
//# sourceMappingURL=valueFilters.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valueFilters.js","names":[],"sources":["../../src/queries/valueFilters.ts"],"sourcesContent":["import { isPlainObject } from \"joist-utils\";\nimport { type Entity, isEntity } from \"src/Entity.ts\";\nimport { isDefined } from \"src/EntityManager.ts\";\nimport type { EntityMetadata } from \"src/EntityMetadata.ts\";\nimport { isAlias } from \"src/queries/find/Aliases.ts\";\nimport type { ValueFilter } from \"src/queries/find/EntityFilter.ts\";\nimport { isScope } from \"src/queries/find/scopes.ts\";\nimport type { ParsedValueFilter } from \"src/queries/parsedConditions.ts\";\nimport type { Column } from \"src/serde/columns.ts\";\nimport { assertNever } from \"src/utils.ts\";\n\n/** An ADT version of `EntityFilter`. */\nexport type ParsedEntityFilter =\n // ParsedValueFilter is any simple match on `id`\n | ParsedValueFilter<string | number>\n // Otherwise we return the join/complex\n | { kind: \"join\"; subFilter: object };\n\n/** Parses an entity filter, which could be \"just an id\", an array of ids, or a nested filter. */\nexport function parseEntityFilter(meta: EntityMetadata, filter: any): ParsedEntityFilter | undefined {\n if (filter === undefined) {\n // This matches legacy `em.find(Book, { author: undefined })` behavior\n return undefined;\n } else if (isAlias(filter)) {\n // We're just binding an alias to this position in the join tree\n return undefined;\n } else if (isScope(filter)) {\n // A scope on a relation (e.g. `{ author: Author.adult }`) always implies conditions on the\n // related entity, so signal \"needs a join\" via the existing `join` discriminant rather than\n // adding a dedicated `kind: \"scope\"` that every relation branch would have to learn. The empty\n // `subFilter` is intentionally never read: the m2o/m2m branches only check `kind === \"join\"` to\n // decide whether to join, then re-dispatch the original scope value back through `addTable`,\n // where `isScope(...)` routes it to `addScopeFilterAt` to apply the scope's actual fragments.\n return { kind: \"join\", subFilter: {} };\n } else if (filter === null) {\n return { kind: \"is-null\" };\n } else if (typeof filter === \"string\" || typeof filter === \"number\") {\n return { kind: \"eq\", value: filter };\n } else if (typeof filter === \"boolean\") {\n return filter ? { kind: \"not-null\" } : { kind: \"is-null\" };\n } else if (Array.isArray(filter)) {\n return {\n kind: \"in\",\n value: filter.map((v: string | number | Entity) => {\n return isEntity(v) ? (v.idTaggedMaybe ?? nilIdValue(meta)) : v;\n }),\n };\n } else if (isEntity(filter)) {\n return { kind: \"eq\", value: filter.idTaggedMaybe || nilIdValue(meta) };\n } else if (typeof filter === \"object\") {\n // Looking for `{ firstName: \"f1\" }` or `{ ne: \"f1\" }`\n const keys = Object.keys(filter);\n // Special case only looking at `ne`\n if (keys.length === 1 && keys[0] === \"ne\") {\n const value = filter[\"ne\"];\n if (value === undefined) {\n return undefined;\n } else if (value === null) {\n return { kind: \"not-null\" };\n } else if (typeof value === \"string\" || typeof value === \"number\") {\n return { kind: \"ne\", value };\n } else if (isEntity(value)) {\n return { kind: \"ne\", value: value.idTaggedMaybe || nilIdValue(meta) };\n } else {\n throw new Error(`Unsupported \"ne\" value ${value}`);\n }\n }\n // Special case only looking at `id`\n if (keys.length === 1 && keys[0] === \"id\") {\n const value = filter[\"id\"];\n if (value === undefined) {\n return undefined;\n } else if (value === null) {\n return { kind: \"is-null\" };\n } else if (typeof value === \"string\" || typeof value === \"number\") {\n return { kind: \"eq\", value };\n } else if (isEntity(value)) {\n return { kind: \"eq\", value: value.idTaggedMaybe || nilIdValue(meta) };\n } else {\n return parseValueFilter(value)[0] as any;\n }\n }\n // Look for subFilter values being EntityFilter-ish instances like ManyToOneReference\n // that have an id, and so structurally match the entity filter without really being filters,\n // and convert them over here before getting into parseValueFilter.\n const subFilter = {} as any;\n for (const [key, value] of Object.entries(filter)) {\n if (value && typeof value === \"object\" && !isPlainObject(value) && \"idTaggedMaybe\" in value) {\n subFilter[key] = value.idTaggedMaybe || nilIdValue(meta);\n } else {\n subFilter[key] = value;\n }\n }\n return { kind: \"join\", subFilter };\n } else {\n throw new Error(`Unrecognized filter ${filter}`);\n }\n}\n\n/**\n * Parses the many/hodgepodge (ergonomic!) patterns of value filters into a `ParsedValueFilter[]`.\n *\n * Note we return an array because filter might be a `ValueGraphQLFilter` that is allowed to have\n * multiple conditions, i.e. `{ lt: 10, gt: 5 }`.\n */\nexport function parseValueFilter<V>(filter: ValueFilter<V, any>): ParsedValueFilter<V>[] {\n if (filter === null) {\n return [{ kind: \"is-null\" }];\n } else if (filter === undefined) {\n // This is legacy behavior where `em.find(Book, { author: undefined })` would match all books\n return [];\n } else if (Array.isArray(filter)) {\n return [{ kind: \"in\", value: filter }];\n } else if (isPlainObject(filter)) {\n const keys = Object.keys(filter);\n if (keys.length === 0) {\n // Should this be an error?\n return [];\n } else if (keys.length === 2 && \"op\" in filter && \"value\" in filter) {\n // Probe for `findGql` op & value\n const { op, value } = filter;\n if (shouldPruneValueFilter(op, value)) {\n return [];\n } else if (value === null) {\n return [{ kind: \"is-null\" }];\n } else {\n return [{ kind: op, value: value ?? null }];\n }\n } else if (keys.length === 2 && \"gte\" in filter && \"lte\" in filter) {\n const { gte, lte } = filter;\n return [{ kind: \"between\", value: [gte, lte] }];\n } else {\n return Object.entries(filter)\n .map(([key, value]) => {\n // Always do condition pruning on the value\n if (shouldPruneValueFilter(key, value)) {\n return undefined;\n }\n switch (key) {\n case \"eq\":\n if (value === null) {\n return { kind: \"is-null\" as const };\n } else {\n return { kind: \"eq\" as const, value: filter[key] };\n }\n case \"ne\":\n if (value === null) {\n return { kind: \"not-null\" as const };\n } else {\n return { kind: \"ne\" as const, value: filter[key] ?? null };\n }\n case \"in\":\n case \"nin\":\n case \"gt\":\n case \"gte\":\n case \"lt\":\n case \"lte\":\n case \"like\":\n case \"nlike\":\n case \"ilike\":\n case \"nilike\":\n case \"regex\":\n case \"nregex\":\n case \"iregex\":\n case \"niregex\":\n case \"contains\":\n case \"overlaps\":\n case \"containedBy\":\n return { kind: key, value: filter[key] };\n case \"pathExists\":\n return { kind: \"jsonPathExists\" as const, value: filter[key] };\n case \"pathIsTrue\":\n return { kind: \"jsonPathPredicate\" as const, value: filter[key] };\n case \"search\":\n return { kind: \"ilike\" as const, value: makeLike(filter[key]) };\n case \"between\":\n return { kind: key, value: filter[key] };\n default:\n throw new Error(`Unsupported value filter key ${key}`);\n }\n })\n .filter(isDefined);\n }\n } else {\n // This is a primitive like a string, number\n return [{ kind: \"eq\", value: filter }];\n }\n}\n\n/** Converts domain-level values like string ids/enums into their db equivalent. */\nexport function mapToDb(column: Column, filter: ParsedValueFilter<any>): ParsedValueFilter<any> {\n // ...to teach this `mapToDb` function to handle/rewrite `in: [1, null]` handling, we'd need to:\n // 1. return a maybe-simple/maybe-nested condition, so basically a `ParsedExpressionCondition`, because\n // this would let `in` return an `{ or: ... }` to all the callers.\n // 2. also return `{ parsed: ParsedExpressionCondition, simples: SimpleCondition[] }` tuple, for the\n // alias `addCondition` processing to track the `simples` and rewrite their alias when later bound.\n switch (filter.kind) {\n case \"eq\":\n case \"gt\":\n case \"gte\":\n case \"ne\":\n case \"lt\":\n case \"lte\":\n case \"like\":\n case \"nlike\":\n case \"ilike\":\n case \"nilike\":\n case \"regex\":\n case \"nregex\":\n case \"iregex\":\n case \"niregex\":\n filter.value = column.mapToDb(filter.value);\n return filter;\n case \"in\":\n if (column.isArray) {\n // Arrays need a special operator\n return { kind: \"contains\", value: column.mapToDb(filter.value) };\n } else {\n filter.value = filter.value.map((v) => column.mapToDb(v));\n }\n return filter;\n case \"nin\":\n if (column.isArray) {\n // Arrays need a special operator\n throw new Error(\"The nin operator is not supported on array columns yet\");\n } else {\n filter.value = filter.value.map((v) => column.mapToDb(v));\n }\n return filter;\n case \"contains\":\n case \"ncontains\":\n case \"overlaps\":\n case \"noverlaps\":\n case \"containedBy\":\n const supportsContains = column.isArray || column.dbType === \"jsonb\";\n if (!supportsContains) {\n throw new Error(`${filter.kind} is only unsupported on array or jsonb columns`);\n }\n if (column.isArray) {\n filter.value = column.mapToDb(filter.value);\n } else {\n // leave jsonb values alone\n }\n return filter;\n case \"between\":\n filter.value = [column.mapToDb(filter.value[0]), column.mapToDb(filter.value[1])];\n return filter;\n case \"is-null\":\n case \"not-null\":\n case \"jsonPathExists\":\n case \"jsonPathPredicate\":\n return filter;\n default:\n throw assertNever(filter);\n }\n}\n\n/** Converts a search term like `foo bar` into a SQL `like` pattern like `%foo%bar%`. */\nexport function makeLike(search: any | undefined): any {\n return search ? `%${search.replace(/\\s+/g, \"%\")}%` : undefined;\n}\n\n/** Recognizes placeholder IDs used when a filter contains an unpersisted entity. */\nexport function isNilIdValue(value: unknown): boolean {\n return value === -1 || value === \"00000000-0000-0000-0000-000000000000\";\n}\n\n/**\n * We use this value if users include new (id-less) entities as em.find conditions.\n *\n * The idea is that this condition would never be met, but we still want to do the em.find\n * query in case it's in an `OR` clause that would match false, but some other part of the\n * clause would match. I.e. instead of just skipping the DB query all together, which is\n * also something we could consider doing.\n *\n * For int IDs we use -1, and for uuid IDs, we use the nil UUID value:\n *\n * https://en.wikipedia.org/wiki/Universally_unique_identifier#Nil_UUID\n */\nfunction nilIdValue(meta: EntityMetadata): any {\n switch (meta.idDbType) {\n case \"int\":\n case \"bigint\":\n return -1;\n case \"uuid\":\n return \"00000000-0000-0000-0000-000000000000\";\n case \"text\":\n return \"0\";\n default:\n return assertNever(meta.idDbType);\n }\n}\n\n/** Returns true for values that should be treated like an omitted filter. */\nfunction shouldPruneValueFilter(key: unknown, value: unknown): boolean {\n if (value === undefined) return true;\n return value === false && key !== \"eq\" && key !== \"ne\";\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"valueFilters.js","names":[],"sources":["../../src/queries/valueFilters.ts"],"sourcesContent":["import { isPlainObject } from \"joist-utils\";\nimport { type Entity, isEntity } from \"src/Entity.ts\";\nimport { isDefined } from \"src/EntityManager.ts\";\nimport type { EntityMetadata } from \"src/EntityMetadata.ts\";\nimport { isAlias } from \"src/queries/find/Aliases.ts\";\nimport type { ValueFilter } from \"src/queries/find/EntityFilter.ts\";\nimport { isScope } from \"src/queries/find/scopes.ts\";\nimport type { ParsedValueFilter } from \"src/queries/parsedConditions.ts\";\nimport type { Column } from \"src/serde/columns.ts\";\nimport { assertNever } from \"src/utils.ts\";\n\n/** An ADT version of `EntityFilter`. */\nexport type ParsedEntityFilter =\n // ParsedValueFilter is any simple match on `id`\n | ParsedValueFilter<string | number>\n // Otherwise we return the join/complex\n | { kind: \"join\"; subFilter: object };\n\n/** Parses an entity filter, which could be \"just an id\", an array of ids, or a nested filter. */\nexport function parseEntityFilter(_meta: EntityMetadata, filter: any): ParsedEntityFilter | undefined {\n if (filter === undefined) {\n // This matches legacy `em.find(Book, { author: undefined })` behavior\n return undefined;\n } else if (isAlias(filter)) {\n // We're just binding an alias to this position in the join tree\n return undefined;\n } else if (isScope(filter)) {\n // A scope on a relation (e.g. `{ author: Author.adult }`) always implies conditions on the\n // related entity, so signal \"needs a join\" via the existing `join` discriminant rather than\n // adding a dedicated `kind: \"scope\"` that every relation branch would have to learn. The empty\n // `subFilter` is intentionally never read: the m2o/m2m branches only check `kind === \"join\"` to\n // decide whether to join, then re-dispatch the original scope value back through `addTable`,\n // where `isScope(...)` routes it to `addScopeFilterAt` to apply the scope's actual fragments.\n return { kind: \"join\", subFilter: {} };\n } else if (filter === null) {\n return { kind: \"is-null\" };\n } else if (typeof filter === \"string\" || typeof filter === \"number\") {\n return { kind: \"eq\", value: filter };\n } else if (typeof filter === \"boolean\") {\n return filter ? { kind: \"not-null\" } : { kind: \"is-null\" };\n } else if (Array.isArray(filter)) {\n return {\n kind: \"in\",\n value: filter.flatMap((value: string | number | Entity) => {\n if (!isEntity(value)) return [value];\n // If no IDs remain, `in: []` marks the condition as unable to match a database row.\n return isNotInDb(value) ? [] : [value.idTagged];\n }),\n };\n } else if (isEntity(filter)) {\n // We use `in: []` to mark an ID-less entity as unable to match a database row.\n return isNotInDb(filter) ? { kind: \"in\", value: [] } : { kind: \"eq\", value: filter.idTagged };\n } else if (typeof filter === \"object\") {\n // Looking for `{ firstName: \"f1\" }` or `{ ne: \"f1\" }`\n const keys = Object.keys(filter);\n // Special case only looking at `ne`\n if (keys.length === 1 && keys[0] === \"ne\") {\n const value = filter[\"ne\"];\n if (value === undefined) {\n return undefined;\n } else if (value === null) {\n return { kind: \"not-null\" };\n } else if (typeof value === \"string\" || typeof value === \"number\") {\n return { kind: \"ne\", value };\n } else if (isEntity(value)) {\n // No persisted FK can equal an ID-less entity, and SQL `!=` excludes nulls, so this is `IS NOT NULL`.\n return isNotInDb(value) ? { kind: \"not-null\" } : { kind: \"ne\", value: value.idTagged };\n } else {\n throw new Error(`Unsupported \"ne\" value ${value}`);\n }\n }\n // Special case only looking at `id`\n if (keys.length === 1 && keys[0] === \"id\") {\n const value = filter[\"id\"];\n if (value === undefined) {\n return undefined;\n } else if (value === null) {\n return { kind: \"is-null\" };\n } else if (typeof value === \"string\" || typeof value === \"number\") {\n return { kind: \"eq\", value };\n } else if (isEntity(value)) {\n return isNotInDb(value) ? { kind: \"in\", value: [] } : { kind: \"eq\", value: value.idTagged };\n } else {\n return parseValueFilter(value)[0] as any;\n }\n }\n // Look for subFilter values being EntityFilter-ish instances like ManyToOneReference\n // that have an id, and so structurally match the entity filter without really being filters,\n // and convert them over here before getting into parseValueFilter.\n const subFilter = {} as any;\n for (const [key, value] of Object.entries(filter)) {\n if (value && typeof value === \"object\" && !isEntity(value) && !isPlainObject(value) && \"idTaggedMaybe\" in value) {\n subFilter[key] = value.idTaggedMaybe === undefined ? [] : value.idTaggedMaybe;\n } else {\n subFilter[key] = value;\n }\n }\n return { kind: \"join\", subFilter };\n } else {\n throw new Error(`Unrecognized filter ${filter}`);\n }\n}\n\n/**\n * Parses the many/hodgepodge (ergonomic!) patterns of value filters into a `ParsedValueFilter[]`.\n *\n * Note we return an array because filter might be a `ValueGraphQLFilter` that is allowed to have\n * multiple conditions, i.e. `{ lt: 10, gt: 5 }`.\n */\nexport function parseValueFilter<V>(filter: ValueFilter<V, any>): ParsedValueFilter<V>[] {\n if (filter === null) {\n return [{ kind: \"is-null\" }];\n } else if (filter === undefined) {\n // This is legacy behavior where `em.find(Book, { author: undefined })` would match all books\n return [];\n } else if (Array.isArray(filter)) {\n return [{ kind: \"in\", value: filter }];\n } else if (isPlainObject(filter)) {\n const keys = Object.keys(filter);\n if (keys.length === 0) {\n // Should this be an error?\n return [];\n } else if (keys.length === 2 && \"op\" in filter && \"value\" in filter) {\n // Probe for `findGql` op & value\n const { op, value } = filter;\n if (shouldPruneValueFilter(op, value)) {\n return [];\n } else if (value === null) {\n return [{ kind: \"is-null\" }];\n } else {\n return [{ kind: op, value: value ?? null }];\n }\n } else if (keys.length === 2 && \"gte\" in filter && \"lte\" in filter) {\n const { gte, lte } = filter;\n return [{ kind: \"between\", value: [gte, lte] }];\n } else {\n return Object.entries(filter)\n .map(([key, value]) => {\n // Always do condition pruning on the value\n if (shouldPruneValueFilter(key, value)) {\n return undefined;\n }\n switch (key) {\n case \"eq\":\n if (value === null) {\n return { kind: \"is-null\" as const };\n } else {\n return { kind: \"eq\" as const, value: filter[key] };\n }\n case \"ne\":\n if (value === null) {\n return { kind: \"not-null\" as const };\n } else {\n return { kind: \"ne\" as const, value: filter[key] ?? null };\n }\n case \"in\":\n case \"nin\":\n case \"gt\":\n case \"gte\":\n case \"lt\":\n case \"lte\":\n case \"like\":\n case \"nlike\":\n case \"ilike\":\n case \"nilike\":\n case \"regex\":\n case \"nregex\":\n case \"iregex\":\n case \"niregex\":\n case \"contains\":\n case \"overlaps\":\n case \"containedBy\":\n return { kind: key, value: filter[key] };\n case \"pathExists\":\n return { kind: \"jsonPathExists\" as const, value: filter[key] };\n case \"pathIsTrue\":\n return { kind: \"jsonPathPredicate\" as const, value: filter[key] };\n case \"search\":\n return { kind: \"ilike\" as const, value: makeLike(filter[key]) };\n case \"between\":\n return { kind: key, value: filter[key] };\n default:\n throw new Error(`Unsupported value filter key ${key}`);\n }\n })\n .filter(isDefined);\n }\n } else {\n // This is a primitive like a string, number\n return [{ kind: \"eq\", value: filter }];\n }\n}\n\n/** Converts domain-level values like string ids/enums into their db equivalent. */\nexport function mapToDb(column: Column, filter: ParsedValueFilter<any>): ParsedValueFilter<any> {\n // ...to teach this `mapToDb` function to handle/rewrite `in: [1, null]` handling, we'd need to:\n // 1. return a maybe-simple/maybe-nested condition, so basically a `ParsedExpressionCondition`, because\n // this would let `in` return an `{ or: ... }` to all the callers.\n // 2. also return `{ parsed: ParsedExpressionCondition, simples: SimpleCondition[] }` tuple, for the\n // alias `addCondition` processing to track the `simples` and rewrite their alias when later bound.\n switch (filter.kind) {\n case \"eq\":\n case \"gt\":\n case \"gte\":\n case \"ne\":\n case \"lt\":\n case \"lte\":\n case \"like\":\n case \"nlike\":\n case \"ilike\":\n case \"nilike\":\n case \"regex\":\n case \"nregex\":\n case \"iregex\":\n case \"niregex\":\n filter.value = column.mapToDb(filter.value);\n return filter;\n case \"in\":\n if (column.isArray) {\n // Arrays need a special operator\n return { kind: \"contains\", value: column.mapToDb(filter.value) };\n } else {\n filter.value = filter.value.map((v) => column.mapToDb(v));\n }\n return filter;\n case \"nin\":\n if (column.isArray) {\n // Arrays need a special operator\n throw new Error(\"The nin operator is not supported on array columns yet\");\n } else {\n filter.value = filter.value.map((v) => column.mapToDb(v));\n }\n return filter;\n case \"contains\":\n case \"ncontains\":\n case \"overlaps\":\n case \"noverlaps\":\n case \"containedBy\":\n const supportsContains = column.isArray || column.dbType === \"jsonb\";\n if (!supportsContains) {\n throw new Error(`${filter.kind} is only unsupported on array or jsonb columns`);\n }\n if (column.isArray) {\n filter.value = column.mapToDb(filter.value);\n } else {\n // leave jsonb values alone\n }\n return filter;\n case \"between\":\n filter.value = [column.mapToDb(filter.value[0]), column.mapToDb(filter.value[1])];\n return filter;\n case \"is-null\":\n case \"not-null\":\n case \"jsonPathExists\":\n case \"jsonPathPredicate\":\n return filter;\n default:\n throw assertNever(filter);\n }\n}\n\n/** Converts a search term like `foo bar` into a SQL `like` pattern like `%foo%bar%`. */\nexport function makeLike(search: any | undefined): any {\n return search ? `%${search.replace(/\\s+/g, \"%\")}%` : undefined;\n}\n\n/** Returns true for values that should be treated like an omitted filter. */\nfunction shouldPruneValueFilter(key: unknown, value: unknown): boolean {\n if (value === undefined) return true;\n return value === false && key !== \"eq\" && key !== \"ne\";\n}\n\n/**\n * Returns whether an entity is known not to be queryable from the database.\n *\n * We check `idTaggedMaybe` instead of `isNewEntity` because commit rules run after INSERTs while those\n * entities still report as new until the flush completes (and yet, because they are in the database, we\n * must keep them in the query instead of ignoring them). An ID can also be assigned before INSERT, so an\n * ID-bearing entity conservatively falls through to a harmless database query.\n */\nfunction isNotInDb(value: Entity): boolean {\n return value.idTaggedMaybe === undefined;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serde.cjs","names":["createRequire","canonicalDbType","arrayOutputType","plainDateMapper","plainTimeMapper","plainDateTimeMapper","getRuntimeConfig","requireTemporal","zonedDateTimeMapper","keyToNumber","maybeResolveReferenceToId","keyToTaggedId","isDefined"],"sources":["../../src/serde/serde.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\n\nimport { isDefined } from \"src/EntityManager.ts\";\nimport { keyToNumber, keyToTaggedId, maybeResolveReferenceToId } from \"src/keys.ts\";\nimport { getRuntimeConfig } from \"src/runtimeConfig.ts\";\nimport { type Temporal, requireTemporal } from \"src/serde/temporal.ts\";\nimport {\n plainDateMapper,\n plainDateTimeMapper,\n plainTimeMapper,\n zonedDateTimeMapper,\n} from \"src/serde/temporalMappers.ts\";\nimport { type TypeInfo, arrayOutputType, canonicalDbType } from \"src/serde/TypeInfo.ts\";\n\nconst runtimeRequire = createRequire(import.meta.url);\n\n/** Scalar conversion only: no field names, entity hydration, or FK fixups. */\nexport interface ScalarCodec {\n dbType: string;\n isArray: boolean;\n /** Compatibility evidence for scalar conversion, absent for unknown codecs. */\n readonly outputType?: TypeInfo;\n /** Encodes a domain value for a filter, i.e. an em.find WHERE parameter. */\n mapToDb(value: any): any;\n /**\n * Encodes a domain value for an INSERT or UPDATE binding.\n *\n * Callers must handle omission, SQL NULL, and SQL expressions before encoding. JSON null and\n * codec defaults can differ from SQL NULL. Direct mutations must validate reference targets\n * and reject new or unflushed entities before encoding.\n */\n mapToDbValue?(value: unknown): unknown;\n /** Converts a driver value to its domain value, including the codec's null/default handling. */\n mapFromDb(value: unknown): unknown;\n /**\n * Converts a json_agg cell to a driver-row value, not a domain value.\n *\n * Lazy field hydration subsequently calls mapFromDb. I.e. Date codecs reconstruct a Date\n * from the JSON string, while Temporal codecs retain the driver's string representation.\n */\n mapFromJsonAgg(value: any): any;\n /** Reconstructs a driver value from a domain value, not a SQL binding. */\n mapToRow(value: any): any;\n}\n\n/** Converts the ORM clock to the timestamp's domain representation. */\nexport interface TimestampCodec<T> extends ScalarCodec {\n mapFromNow(now: Date): T;\n}\n\n/**\n * Provides a simplified, public API for mapping between db/domain values.\n *\n * Joist's internal `FieldSerde` API is admittedly a little crufty, and so this\n * API is intended to be a simpler, more user-friendly way to define custom types.\n */\nexport interface CustomSerde<DomainType, DbType> {\n toDb(value: DomainType): DbType;\n fromDb(value: DbType): DomainType;\n}\n\nexport class CustomSerdeAdapter implements ScalarCodec {\n isArray: boolean = false;\n\n constructor(\n public dbType: string,\n private mapper: CustomSerde<any, any>,\n isArray?: boolean,\n ) {\n if (isArray !== undefined) this.isArray = isArray;\n }\n\n /** Only known adapters share their mapper's identity and elementwise array conversion. */\n get outputType(): TypeInfo | undefined {\n if (\n this.isArray !== this.dbType.endsWith(\"[]\") ||\n (this.constructor !== CustomSerdeAdapter &&\n this.constructor !== PlainDateSerde &&\n this.constructor !== PlainTimeSerde &&\n this.constructor !== PlainDateTimeSerde &&\n this.constructor !== ZonedDateTimeSerde)\n ) {\n return undefined;\n }\n const dbType = canonicalDbType(this.dbType);\n // A numeric mapper may require scalar text; classic pg decodes numeric[] elements as numbers.\n const elementType = this.isArray ? dbType.slice(0, -2) : dbType;\n const outputType = { dbType: elementType, domain: this.mapper, arrayElementSafe: elementType !== \"numeric\" };\n if (!this.isArray) return outputType;\n // Physical arrays pass null elements to the mapper, unlike scalar arrayAgg; keep their domains distinct.\n return arrayOutputType(outputType) || [\"date\", \"time\", \"timestamp\", \"timestamptz\"].includes(elementType)\n ? { dbType, domain: this.mapper }\n : undefined;\n }\n\n /** Encodes each physical array element once. */\n mapToDbValue(value: unknown): unknown {\n return value !== undefined\n ? this.isArray\n ? (value as readonly unknown[]).map((element) => this.mapper.toDb(element))\n : this.mapper.toDb(value)\n : undefined;\n }\n\n mapToRow(value: any): any {\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any): any {\n return value === null\n ? value\n : this.isArray\n ? value.map((element: unknown) => this.mapper.toDb(element))\n : this.mapper.toDb(value);\n }\n\n /** Converts a driver value with the custom mapper, including array elements. */\n mapFromDb(value: unknown): unknown {\n const dbValue = maybeNullToUndefined(value);\n return dbValue !== undefined\n ? this.isArray\n ? dbValue.map((value: unknown) => this.mapper.fromDb(value))\n : this.mapper.fromDb(dbValue)\n : undefined;\n }\n\n mapFromJsonAgg(value: any): any {\n // Assume the database JSON value matches the ResultSet value\n return value;\n // return value === null\n // ? value\n // : this.isArray\n // ? value.map((value: any) => this.mapper.fromDb(value))\n // : this.mapper.fromDb(value);\n }\n}\n\n/**\n * Supports `string`, `int`, etc., as well as `string[]`, `int[]`, etc.\n *\n * This is not generally meant for subclassing, because it assumes things like\n * `string[]`s can be mapped 1:1. See `CustomSerdeAdapter` a good base class\n * that will handle converting individual elements.\n */\nexport class PrimitiveSerde implements ScalarCodec {\n constructor(\n public dbType: string,\n public isArray = false,\n ) {}\n\n /** Exact-class checks prevent an unknown subclass's overridden converters from sharing this codec. */\n get outputType(): TypeInfo | undefined {\n if (this.constructor !== PrimitiveSerde) return undefined;\n const dbType = canonicalDbType(this.dbType);\n const elementType = dbType.endsWith(\"[]\") ? dbType.slice(0, -2) : dbType;\n if (this.isArray !== dbType.endsWith(\"[]\")) return undefined;\n const outputType = {\n dbType: elementType,\n domain: primitiveOutputDomain(elementType),\n arrayElementSafe: elementType !== \"numeric\",\n };\n return this.isArray ? arrayOutputType(outputType) : outputType;\n }\n\n /** Primitive writes share their scalar/filter conversion, including subclass overrides. */\n mapToDbValue(value: unknown): unknown {\n return this.mapToDb(value);\n }\n\n mapToRow(value: any): any {\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any) {\n return value;\n }\n\n /** Keeps driver values as-is, with SQL NULL represented as an unset entity field. */\n mapFromDb(value: unknown): unknown {\n return maybeNullToUndefined(value);\n }\n\n mapFromJsonAgg(value: any): any {\n return value;\n }\n}\n\nexport class DateSerde extends PrimitiveSerde implements TimestampCodec<Date> {\n /** Date bindings use ISO strings; mapToDb does not support physical Date arrays. */\n get outputType(): TypeInfo | undefined {\n return this.constructor === DateSerde && !this.isArray && !this.dbType.endsWith(\"[]\")\n ? { dbType: canonicalDbType(this.dbType), domain: DateSerde }\n : undefined;\n }\n\n /** Accept the caller's date as-is. */\n mapFromNow(now: Date): Date {\n return now;\n }\n\n mapFromJsonAgg(value: any): any {\n if (value === null) return value;\n return new Date(value);\n }\n\n /**\n * Returns the `Date` a driver hands back on a read, and not `mapToDb`'s ISO string.\n *\n * Otherwise callers that round-trip a row through `rowValue` and back through `fromRow` (i.e.\n * `RunPlugin` mirroring writes into the test em) turn every date into a string, because our\n * `mapFromDb` returns the row value as-is.\n */\n mapToRow(value: any): any {\n return value;\n }\n\n mapToDb(value: Date) {\n // bun.sql needs this, node-pg does it out of the box\n return value?.toISOString();\n }\n}\n\n/** Converts `DATE`s `Temporal.PlainDate`s. */\nexport class PlainDateSerde extends CustomSerdeAdapter {\n constructor(dbType: string, isArray = false) {\n super(dbType, plainDateMapper, isArray);\n }\n}\n\n/** Converts `TIME`s to `Temporal.PlainTime`s. */\nexport class PlainTimeSerde extends CustomSerdeAdapter {\n constructor(dbType: string, isArray = false) {\n super(dbType, plainTimeMapper, isArray);\n }\n}\n\n/** Converts `TIMESTAMP`s to `Temporal.PlainDateTime`s. */\nexport class PlainDateTimeSerde extends CustomSerdeAdapter implements TimestampCodec<Temporal.PlainDateTime> {\n constructor(dbType: string, isArray = false) {\n super(dbType, plainDateTimeMapper, isArray);\n }\n\n mapFromNow(now: Date): Temporal.PlainDateTime {\n const { timeZone } = getRuntimeConfig().temporal as any;\n return requireTemporal().toTemporalInstant.call(now).toZonedDateTimeISO(timeZone).toPlainDateTime();\n }\n}\n\n/** Converts `TIMESTAMP WITH TIME ZONE`s to `Temporal.ZonedDateTime`s. */\nexport class ZonedDateTimeSerde extends CustomSerdeAdapter implements TimestampCodec<Temporal.ZonedDateTime> {\n constructor(dbType: string, isArray = false) {\n super(dbType, zonedDateTimeMapper, isArray);\n }\n\n mapFromNow(now: Date): Temporal.ZonedDateTime {\n const { timeZone } = getRuntimeConfig().temporal as any;\n return requireTemporal().toTemporalInstant.call(now).toZonedDateTimeISO(timeZone);\n }\n}\n\nexport class BigIntSerde implements ScalarCodec {\n dbType: string;\n\n constructor(public isArray = false) {\n this.dbType = isArray ? \"bigint[]\" : \"bigint\";\n }\n\n /** BigInt conversion is distinct from both the driver's int8 value and Number aggregates. */\n get outputType(): TypeInfo | undefined {\n if (this.constructor !== BigIntSerde) return undefined;\n const elementType = { dbType: \"int8\", domain: BigIntSerde, arrayElementSafe: true };\n return this.isArray ? arrayOutputType(elementType) : elementType;\n }\n\n /** Drivers accept native bigint values without conversion. */\n mapToDbValue(value: unknown): unknown {\n if (this.isArray) assertFlatNumericArray(value);\n return value;\n }\n\n mapToRow(value: any): any {\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any) {\n return value;\n }\n\n /** Converts the driver's bigint representation to the entity value. */\n mapFromDb(value: unknown): unknown {\n const dbValue = maybeNullToUndefined(value);\n if (this.isArray && dbValue !== undefined) {\n assertFlatNumericArray(dbValue);\n return dbValue.map((element: string | bigint | number | null) => (element === null ? null : BigInt(element)));\n }\n return dbValue ? BigInt(dbValue) : dbValue;\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : this.isArray ? this.mapFromDb(value) : BigInt(value);\n }\n}\n\n/**\n * Maps `decimal(...)` database types to the JS `number`.\n *\n * Note that we assume the db values are within the range of the JS `number`;\n * we should eventually sanity check that.\n *\n * Also note that knex/pg accept `number`s as input, so we only need\n * to handle from-database -> to JS translation.\n */\nexport class DecimalToNumberSerde implements ScalarCodec {\n dbType: string;\n\n constructor(public isArray = false) {\n this.dbType = isArray ? \"numeric[]\" : \"decimal\";\n }\n\n /** Numeric aggregates use the same Number conversion and identity binding encoder. */\n get outputType(): TypeInfo | undefined {\n if (this.constructor !== DecimalToNumberSerde) return undefined;\n const elementType = { dbType: \"numeric\", domain: Number, arrayElementSafe: true };\n return this.isArray ? arrayOutputType(elementType) : elementType;\n }\n\n /** Drivers accept decimal numbers without conversion. */\n mapToDbValue(value: unknown): unknown {\n if (this.isArray) assertFlatNumericArray(value);\n return value;\n }\n\n mapToRow(value: any): any {\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any) {\n return value;\n }\n\n /** Converts a driver decimal value to a JavaScript number. */\n mapFromDb(value: unknown): unknown {\n const dbValue = maybeNullToUndefined(value);\n if (this.isArray && dbValue !== undefined) {\n assertFlatNumericArray(dbValue);\n return dbValue.map((element: string | number | null) => (element === null ? null : Number(element)));\n }\n return dbValue !== undefined ? Number(dbValue) : dbValue;\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : this.isArray ? this.mapFromDb(value) : Number(value);\n }\n}\n\n/** Maps physical integer keys to logical string IDs \"because GraphQL\". */\nexport class KeySerde implements ScalarCodec {\n isArray = false;\n\n private meta: {\n tagName: string;\n idDbType: \"bigint\" | \"int\" | \"uuid\" | \"text\";\n };\n\n constructor(\n tagName: string,\n public dbType: \"bigint\" | \"int\" | \"uuid\" | \"text\",\n ) {\n this.meta = {\n tagName,\n idDbType: dbType,\n };\n }\n\n /** PKs and FKs share a tag and SQL representation; the alias supplies the exact target idMeta. */\n get outputType(): TypeInfo | undefined {\n return this.constructor === KeySerde && !this.isArray\n ? { dbType: canonicalDbType(this.dbType), domain: `key:${this.meta.tagName}`, arrayElementSafe: true }\n : undefined;\n }\n\n /** Resolves references and tagged, untagged, or numeric ids using the column's tag and storage type. */\n mapToDbValue(value: unknown): unknown {\n return keyToNumber(\n this.meta,\n typeof value === \"number\"\n ? value\n : maybeResolveReferenceToId(value as Parameters<typeof maybeResolveReferenceToId>[0]),\n );\n }\n\n mapToRow(value: any): any {\n // we don't have any fixups since we are trying to recreate what comes out of the db, so this is safe\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any) {\n // Sometimes the nilIdValue will pass -1 as already a number, but usually this should be a tagged id\n if (value === null || typeof value === \"number\") return value;\n // We go through `maybeResolveReferenceToId` because filters like `in: [a1, a2]` pass entities directly.\n return keyToNumber(this.meta, maybeResolveReferenceToId(value));\n }\n\n /** Converts a physical primary or foreign key to a tagged id. */\n mapFromDb(value: unknown): string | undefined {\n return keyToTaggedId(this.meta, value as string | number);\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : value;\n }\n}\n\nexport class EnumFieldSerde implements ScalarCodec {\n isArray = false;\n\n constructor(\n public dbType: \"int\" | \"uuid\",\n private enumObject: any,\n ) {}\n\n /** Separate fields of the same generated enum share its code/id mapping. */\n get outputType(): TypeInfo | undefined {\n return this.constructor === EnumFieldSerde && !this.isArray\n ? { dbType: canonicalDbType(this.dbType), domain: this.enumObject, arrayElementSafe: true }\n : undefined;\n }\n\n /** Resolves the enum code to its stored id, leaving an unknown code unset. */\n mapToDbValue(value: unknown): unknown {\n return this.enumObject.findByCode(value)?.id;\n }\n\n mapToRow(value: any): any {\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any) {\n return this.enumObject.findByCode(value)?.id;\n }\n\n /** Converts a stored enum id to its enum code. */\n mapFromDb(value: unknown): unknown {\n return this.enumObject.findById(value)?.code;\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : value;\n }\n}\n\nexport class EnumArrayFieldSerde implements ScalarCodec {\n isArray = true;\n\n constructor(\n public dbType: \"int[]\" | \"uuid[]\",\n private enumObject: any,\n ) {}\n\n /** Physical enum arrays reject null elements, unlike scalar enum arrayAgg; leave them unknown. */\n get outputType(): undefined {\n return undefined;\n }\n\n /** Resolves each enum code to its stored id, retaining the entity's empty-array default. */\n mapToDbValue(value: unknown): unknown {\n return (value as readonly unknown[] | null | undefined)?.map((code) => this.enumObject.getByCode(code).id) || [];\n }\n\n mapToRow(value: any): any {\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any) {\n return !value ? [] : value.map((code: any) => this.enumObject.getByCode(code).id);\n }\n\n /** Converts stored enum ids to codes, defaulting an unset entity field to an empty array. */\n mapFromDb(value: unknown): unknown[] {\n return (value as readonly unknown[] | null | undefined)?.map((id) => this.enumObject.findById(id).code) || [];\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : value;\n }\n}\n\n/** Similar to SimpleSerde, but applies the superstruct `assert` function when reading values from the db. */\nexport class SuperstructSerde implements ScalarCodec {\n dbType = \"jsonb\";\n isArray = false;\n\n // Use a dynamic require so that downstream projects don't have to depend on superstruct\n // until they want to, i.e. we don't have superstruct in the joist-orm package.json.\n private assert = runtimeRequire(\"superstruct\").assert;\n\n constructor(private superstruct: any) {}\n\n /** JSON schemas describe a jsonb value, even when that value is a JSON array. */\n get outputType(): TypeInfo | undefined {\n return this.constructor === SuperstructSerde && !this.isArray\n ? { dbType: canonicalDbType(this.dbType), domain: this.superstruct, arrayElementSafe: true }\n : undefined;\n }\n\n /** Serializes the domain JSON value without running the read-time schema validation. */\n mapToDbValue(value: unknown): unknown {\n return JSON.stringify(value);\n }\n\n // JSON is returned by postgres already parsed, so we should just be able to return our data directly. Unlike with\n // JsonSerde, we can assume that superstruct would have parsed any complex types in the json correctly so we don't\n // need to do a round trip through JSON.stringify.\n mapToRow(value: any): any {\n return value;\n }\n\n mapToDb(value: any) {\n return JSON.stringify(value);\n }\n\n /** Validates a driver JSON value with the field's Superstruct schema. */\n mapFromDb(value: unknown): unknown {\n const dbValue = maybeNullToUndefined(value);\n if (dbValue) this.assert(dbValue, this.superstruct);\n return dbValue;\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : value;\n }\n}\n\nexport class JsonSerde implements ScalarCodec {\n dbType = \"jsonb\";\n isArray = false;\n\n constructor() {}\n\n /** Unvalidated JSON uses its own encoder, not a primitive or schema-specific codec. */\n get outputType(): TypeInfo | undefined {\n return this.constructor === JsonSerde && !this.isArray\n ? { dbType: canonicalDbType(this.dbType), domain: JsonSerde, arrayElementSafe: true }\n : undefined;\n }\n\n /** Serializes the whole JSON value, including JSON arrays and custom toJSON methods. */\n mapToDbValue(value: unknown): unknown {\n return JSON.stringify(value);\n }\n\n // JSON is returned by postgres already parsed, so if we are trying to recreate then we need to stringify, then parse.\n // It's necessary to do this instead of just returning the object directly because any complex types in the json need\n // to be stringified to correctly reflect the db value.\n mapToRow(value: any): any {\n const json = JSON.stringify(value);\n return isDefined(json) ? JSON.parse(json) : undefined;\n }\n\n mapToDb(value: any) {\n return JSON.stringify(value);\n }\n\n /** Keeps the driver's parsed JSON value, normalizing SQL NULL for entity fields. */\n mapFromDb(value: unknown): unknown {\n return maybeNullToUndefined(value);\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : value;\n }\n}\n\n/** Similar to SimpleSerde, but applies the zod's `parse` function when reading values from the db. */\nexport class ZodSerde implements ScalarCodec {\n dbType = \"jsonb\";\n isArray = false;\n\n constructor(private zodSchema: any) {}\n\n /** Parsing and transformations are compatible only for the same schema object. */\n get outputType(): TypeInfo | undefined {\n return this.constructor === ZodSerde && !this.isArray\n ? { dbType: canonicalDbType(this.dbType), domain: this.zodSchema, arrayElementSafe: true }\n : undefined;\n }\n\n /** Serializes the domain JSON value without running the read-time Zod parser or transforms. */\n mapToDbValue(value: unknown): unknown {\n return JSON.stringify(value);\n }\n\n // JSON is returned by postgres already parsed, so we should just be able to return our data directly. Unlike with\n // JsonSerde, we can assume that zod would have parsed any complex types in the json correctly so we don't need to\n // do a round trip through JSON.stringify.\n mapToRow(value: any): any {\n return value;\n }\n\n mapToDb(value: any) {\n return JSON.stringify(value);\n }\n\n /** Parses a driver JSON value with the field's Zod schema. */\n mapFromDb(value: unknown): unknown {\n const dbValue = maybeNullToUndefined(value);\n return dbValue ? this.zodSchema.parse(dbValue) : dbValue;\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : value;\n }\n}\n\n/** PostgreSQL does not enforce declared array dimensions; reject nested values rather than coercing subarrays. */\nfunction assertFlatNumericArray(value: unknown): void {\n if (Array.isArray(value) && value.some(Array.isArray)) {\n throw new Error(\"Native numeric arrays must be one-dimensional\");\n }\n}\n\n/** Only driver-native numbers agree with Number aggregates; int8/numeric identity values do not. */\nfunction primitiveOutputDomain(dbType: string): unknown {\n switch (dbType) {\n case \"int2\":\n case \"int4\":\n case \"float4\":\n case \"float8\":\n return Number;\n case \"text\":\n case \"varchar\":\n case \"bpchar\":\n return String;\n default:\n return PrimitiveSerde;\n }\n}\n\n/** Normalizes SQL NULL to an unset entity field. */\nfunction maybeNullToUndefined(value: any): any {\n return value === null ? undefined : value;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"serde.cjs","names":["createRequire","canonicalDbType","arrayOutputType","plainDateMapper","plainTimeMapper","plainDateTimeMapper","getRuntimeConfig","requireTemporal","zonedDateTimeMapper","keyToNumber","maybeResolveReferenceToId","keyToTaggedId","isDefined"],"sources":["../../src/serde/serde.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\n\nimport { isDefined } from \"src/EntityManager.ts\";\nimport { keyToNumber, keyToTaggedId, maybeResolveReferenceToId } from \"src/keys.ts\";\nimport { getRuntimeConfig } from \"src/runtimeConfig.ts\";\nimport { type Temporal, requireTemporal } from \"src/serde/temporal.ts\";\nimport {\n plainDateMapper,\n plainDateTimeMapper,\n plainTimeMapper,\n zonedDateTimeMapper,\n} from \"src/serde/temporalMappers.ts\";\nimport { type TypeInfo, arrayOutputType, canonicalDbType } from \"src/serde/TypeInfo.ts\";\n\nconst runtimeRequire = createRequire(import.meta.url);\n\n/** Scalar conversion only: no field names, entity hydration, or FK fixups. */\nexport interface ScalarCodec {\n dbType: string;\n isArray: boolean;\n /** Compatibility evidence for scalar conversion, absent for unknown codecs. */\n readonly outputType?: TypeInfo;\n /** Encodes a domain value for a filter, i.e. an em.find WHERE parameter. */\n mapToDb(value: any): any;\n /**\n * Encodes a domain value for an INSERT or UPDATE binding.\n *\n * Callers must handle omission, SQL NULL, and SQL expressions before encoding. JSON null and\n * codec defaults can differ from SQL NULL. Direct mutations must validate reference targets\n * and reject new or unflushed entities before encoding.\n */\n mapToDbValue?(value: unknown): unknown;\n /** Converts a driver value to its domain value, including the codec's null/default handling. */\n mapFromDb(value: unknown): unknown;\n /**\n * Converts a json_agg cell to a driver-row value, not a domain value.\n *\n * Lazy field hydration subsequently calls mapFromDb. I.e. Date codecs reconstruct a Date\n * from the JSON string, while Temporal codecs retain the driver's string representation.\n */\n mapFromJsonAgg(value: any): any;\n /** Reconstructs a driver value from a domain value, not a SQL binding. */\n mapToRow(value: any): any;\n}\n\n/** Converts the ORM clock to the timestamp's domain representation. */\nexport interface TimestampCodec<T> extends ScalarCodec {\n mapFromNow(now: Date): T;\n}\n\n/**\n * Provides a simplified, public API for mapping between db/domain values.\n *\n * Joist's internal `FieldSerde` API is admittedly a little crufty, and so this\n * API is intended to be a simpler, more user-friendly way to define custom types.\n */\nexport interface CustomSerde<DomainType, DbType> {\n toDb(value: DomainType): DbType;\n fromDb(value: DbType): DomainType;\n}\n\nexport class CustomSerdeAdapter implements ScalarCodec {\n isArray: boolean = false;\n\n constructor(\n public dbType: string,\n private mapper: CustomSerde<any, any>,\n isArray?: boolean,\n ) {\n if (isArray !== undefined) this.isArray = isArray;\n }\n\n /** Only known adapters share their mapper's identity and elementwise array conversion. */\n get outputType(): TypeInfo | undefined {\n if (\n this.isArray !== this.dbType.endsWith(\"[]\") ||\n (this.constructor !== CustomSerdeAdapter &&\n this.constructor !== PlainDateSerde &&\n this.constructor !== PlainTimeSerde &&\n this.constructor !== PlainDateTimeSerde &&\n this.constructor !== ZonedDateTimeSerde)\n ) {\n return undefined;\n }\n const dbType = canonicalDbType(this.dbType);\n // A numeric mapper may require scalar text; classic pg decodes numeric[] elements as numbers.\n const elementType = this.isArray ? dbType.slice(0, -2) : dbType;\n const outputType = { dbType: elementType, domain: this.mapper, arrayElementSafe: elementType !== \"numeric\" };\n if (!this.isArray) return outputType;\n // Physical arrays pass null elements to the mapper, unlike scalar arrayAgg; keep their domains distinct.\n return arrayOutputType(outputType) || [\"date\", \"time\", \"timestamp\", \"timestamptz\"].includes(elementType)\n ? { dbType, domain: this.mapper }\n : undefined;\n }\n\n /** Encodes each physical array element once. */\n mapToDbValue(value: unknown): unknown {\n return value !== undefined\n ? this.isArray\n ? (value as readonly unknown[]).map((element) => this.mapper.toDb(element))\n : this.mapper.toDb(value)\n : undefined;\n }\n\n mapToRow(value: any): any {\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any): any {\n return value === null\n ? value\n : this.isArray\n ? value.map((element: unknown) => this.mapper.toDb(element))\n : this.mapper.toDb(value);\n }\n\n /** Converts a driver value with the custom mapper, including array elements. */\n mapFromDb(value: unknown): unknown {\n const dbValue = maybeNullToUndefined(value);\n return dbValue !== undefined\n ? this.isArray\n ? dbValue.map((value: unknown) => this.mapper.fromDb(value))\n : this.mapper.fromDb(dbValue)\n : undefined;\n }\n\n mapFromJsonAgg(value: any): any {\n // Assume the database JSON value matches the ResultSet value\n return value;\n // return value === null\n // ? value\n // : this.isArray\n // ? value.map((value: any) => this.mapper.fromDb(value))\n // : this.mapper.fromDb(value);\n }\n}\n\n/**\n * Supports `string`, `int`, etc., as well as `string[]`, `int[]`, etc.\n *\n * This is not generally meant for subclassing, because it assumes things like\n * `string[]`s can be mapped 1:1. See `CustomSerdeAdapter` a good base class\n * that will handle converting individual elements.\n */\nexport class PrimitiveSerde implements ScalarCodec {\n constructor(\n public dbType: string,\n public isArray = false,\n ) {}\n\n /** Exact-class checks prevent an unknown subclass's overridden converters from sharing this codec. */\n get outputType(): TypeInfo | undefined {\n if (this.constructor !== PrimitiveSerde) return undefined;\n const dbType = canonicalDbType(this.dbType);\n const elementType = dbType.endsWith(\"[]\") ? dbType.slice(0, -2) : dbType;\n if (this.isArray !== dbType.endsWith(\"[]\")) return undefined;\n const outputType = {\n dbType: elementType,\n domain: primitiveOutputDomain(elementType),\n arrayElementSafe: elementType !== \"numeric\",\n };\n return this.isArray ? arrayOutputType(outputType) : outputType;\n }\n\n /** Primitive writes share their scalar/filter conversion, including subclass overrides. */\n mapToDbValue(value: unknown): unknown {\n return this.mapToDb(value);\n }\n\n mapToRow(value: any): any {\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any) {\n return value;\n }\n\n /** Keeps driver values as-is, with SQL NULL represented as an unset entity field. */\n mapFromDb(value: unknown): unknown {\n return maybeNullToUndefined(value);\n }\n\n mapFromJsonAgg(value: any): any {\n return value;\n }\n}\n\nexport class DateSerde extends PrimitiveSerde implements TimestampCodec<Date> {\n /** Date bindings use ISO strings; mapToDb does not support physical Date arrays. */\n get outputType(): TypeInfo | undefined {\n return this.constructor === DateSerde && !this.isArray && !this.dbType.endsWith(\"[]\")\n ? { dbType: canonicalDbType(this.dbType), domain: DateSerde }\n : undefined;\n }\n\n /** Accept the caller's date as-is. */\n mapFromNow(now: Date): Date {\n return now;\n }\n\n mapFromJsonAgg(value: any): any {\n if (value === null) return value;\n return new Date(value);\n }\n\n /**\n * Returns the `Date` a driver hands back on a read, and not `mapToDb`'s ISO string.\n *\n * Otherwise callers that round-trip a row through `rowValue` and back through `fromRow` (i.e.\n * `RunPlugin` mirroring writes into the test em) turn every date into a string, because our\n * `mapFromDb` returns the row value as-is.\n */\n mapToRow(value: any): any {\n return value;\n }\n\n mapToDb(value: Date) {\n // bun.sql needs this, node-pg does it out of the box\n return value?.toISOString();\n }\n}\n\n/** Converts `DATE`s `Temporal.PlainDate`s. */\nexport class PlainDateSerde extends CustomSerdeAdapter {\n constructor(dbType: string, isArray = false) {\n super(dbType, plainDateMapper, isArray);\n }\n}\n\n/** Converts `TIME`s to `Temporal.PlainTime`s. */\nexport class PlainTimeSerde extends CustomSerdeAdapter {\n constructor(dbType: string, isArray = false) {\n super(dbType, plainTimeMapper, isArray);\n }\n}\n\n/** Converts `TIMESTAMP`s to `Temporal.PlainDateTime`s. */\nexport class PlainDateTimeSerde extends CustomSerdeAdapter implements TimestampCodec<Temporal.PlainDateTime> {\n constructor(dbType: string, isArray = false) {\n super(dbType, plainDateTimeMapper, isArray);\n }\n\n mapFromNow(now: Date): Temporal.PlainDateTime {\n const { timeZone } = getRuntimeConfig().temporal as any;\n return requireTemporal().toTemporalInstant.call(now).toZonedDateTimeISO(timeZone).toPlainDateTime();\n }\n}\n\n/** Converts `TIMESTAMP WITH TIME ZONE`s to `Temporal.ZonedDateTime`s. */\nexport class ZonedDateTimeSerde extends CustomSerdeAdapter implements TimestampCodec<Temporal.ZonedDateTime> {\n constructor(dbType: string, isArray = false) {\n super(dbType, zonedDateTimeMapper, isArray);\n }\n\n mapFromNow(now: Date): Temporal.ZonedDateTime {\n const { timeZone } = getRuntimeConfig().temporal as any;\n return requireTemporal().toTemporalInstant.call(now).toZonedDateTimeISO(timeZone);\n }\n}\n\nexport class BigIntSerde implements ScalarCodec {\n dbType: string;\n\n constructor(public isArray = false) {\n this.dbType = isArray ? \"bigint[]\" : \"bigint\";\n }\n\n /** BigInt conversion is distinct from both the driver's int8 value and Number aggregates. */\n get outputType(): TypeInfo | undefined {\n if (this.constructor !== BigIntSerde) return undefined;\n const elementType = { dbType: \"int8\", domain: BigIntSerde, arrayElementSafe: true };\n return this.isArray ? arrayOutputType(elementType) : elementType;\n }\n\n /** Drivers accept native bigint values without conversion. */\n mapToDbValue(value: unknown): unknown {\n if (this.isArray) assertFlatNumericArray(value);\n return value;\n }\n\n mapToRow(value: any): any {\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any) {\n return value;\n }\n\n /** Converts the driver's bigint representation to the entity value. */\n mapFromDb(value: unknown): unknown {\n const dbValue = maybeNullToUndefined(value);\n if (this.isArray && dbValue !== undefined) {\n assertFlatNumericArray(dbValue);\n return dbValue.map((element: string | bigint | number | null) => (element === null ? null : BigInt(element)));\n }\n return dbValue ? BigInt(dbValue) : dbValue;\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : this.isArray ? this.mapFromDb(value) : BigInt(value);\n }\n}\n\n/**\n * Maps `decimal(...)` database types to the JS `number`.\n *\n * Note that we assume the db values are within the range of the JS `number`;\n * we should eventually sanity check that.\n *\n * Also note that knex/pg accept `number`s as input, so we only need\n * to handle from-database -> to JS translation.\n */\nexport class DecimalToNumberSerde implements ScalarCodec {\n dbType: string;\n\n constructor(public isArray = false) {\n this.dbType = isArray ? \"numeric[]\" : \"decimal\";\n }\n\n /** Numeric aggregates use the same Number conversion and identity binding encoder. */\n get outputType(): TypeInfo | undefined {\n if (this.constructor !== DecimalToNumberSerde) return undefined;\n const elementType = { dbType: \"numeric\", domain: Number, arrayElementSafe: true };\n return this.isArray ? arrayOutputType(elementType) : elementType;\n }\n\n /** Drivers accept decimal numbers without conversion. */\n mapToDbValue(value: unknown): unknown {\n if (this.isArray) assertFlatNumericArray(value);\n return value;\n }\n\n mapToRow(value: any): any {\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any) {\n return value;\n }\n\n /** Converts a driver decimal value to a JavaScript number. */\n mapFromDb(value: unknown): unknown {\n const dbValue = maybeNullToUndefined(value);\n if (this.isArray && dbValue !== undefined) {\n assertFlatNumericArray(dbValue);\n return dbValue.map((element: string | number | null) => (element === null ? null : Number(element)));\n }\n return dbValue !== undefined ? Number(dbValue) : dbValue;\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : this.isArray ? this.mapFromDb(value) : Number(value);\n }\n}\n\n/** Maps physical integer keys to logical string IDs \"because GraphQL\". */\nexport class KeySerde implements ScalarCodec {\n isArray = false;\n\n private meta: {\n tagName: string;\n idDbType: \"bigint\" | \"int\" | \"uuid\" | \"text\";\n };\n\n constructor(\n tagName: string,\n public dbType: \"bigint\" | \"int\" | \"uuid\" | \"text\",\n ) {\n this.meta = {\n tagName,\n idDbType: dbType,\n };\n }\n\n /** PKs and FKs share a tag and SQL representation; the alias supplies the exact target idMeta. */\n get outputType(): TypeInfo | undefined {\n return this.constructor === KeySerde && !this.isArray\n ? { dbType: canonicalDbType(this.dbType), domain: `key:${this.meta.tagName}`, arrayElementSafe: true }\n : undefined;\n }\n\n /** Resolves references and tagged, untagged, or numeric ids using the column's tag and storage type. */\n mapToDbValue(value: unknown): unknown {\n return keyToNumber(\n this.meta,\n typeof value === \"number\"\n ? value\n : maybeResolveReferenceToId(value as Parameters<typeof maybeResolveReferenceToId>[0]),\n );\n }\n\n mapToRow(value: any): any {\n // we don't have any fixups since we are trying to recreate what comes out of the db, so this is safe\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any) {\n // Numeric IDs are already database values; tagged string IDs still need to be resolved and untagged.\n if (value === null || typeof value === \"number\") return value;\n // We go through `maybeResolveReferenceToId` because filters like `in: [a1, a2]` pass entities directly.\n return keyToNumber(this.meta, maybeResolveReferenceToId(value));\n }\n\n /** Converts a physical primary or foreign key to a tagged id. */\n mapFromDb(value: unknown): string | undefined {\n return keyToTaggedId(this.meta, value as string | number);\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : value;\n }\n}\n\nexport class EnumFieldSerde implements ScalarCodec {\n isArray = false;\n\n constructor(\n public dbType: \"int\" | \"uuid\",\n private enumObject: any,\n ) {}\n\n /** Separate fields of the same generated enum share its code/id mapping. */\n get outputType(): TypeInfo | undefined {\n return this.constructor === EnumFieldSerde && !this.isArray\n ? { dbType: canonicalDbType(this.dbType), domain: this.enumObject, arrayElementSafe: true }\n : undefined;\n }\n\n /** Resolves the enum code to its stored id, leaving an unknown code unset. */\n mapToDbValue(value: unknown): unknown {\n return this.enumObject.findByCode(value)?.id;\n }\n\n mapToRow(value: any): any {\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any) {\n return this.enumObject.findByCode(value)?.id;\n }\n\n /** Converts a stored enum id to its enum code. */\n mapFromDb(value: unknown): unknown {\n return this.enumObject.findById(value)?.code;\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : value;\n }\n}\n\nexport class EnumArrayFieldSerde implements ScalarCodec {\n isArray = true;\n\n constructor(\n public dbType: \"int[]\" | \"uuid[]\",\n private enumObject: any,\n ) {}\n\n /** Physical enum arrays reject null elements, unlike scalar enum arrayAgg; leave them unknown. */\n get outputType(): undefined {\n return undefined;\n }\n\n /** Resolves each enum code to its stored id, retaining the entity's empty-array default. */\n mapToDbValue(value: unknown): unknown {\n return (value as readonly unknown[] | null | undefined)?.map((code) => this.enumObject.getByCode(code).id) || [];\n }\n\n mapToRow(value: any): any {\n return this.mapToDbValue(value);\n }\n\n mapToDb(value: any) {\n return !value ? [] : value.map((code: any) => this.enumObject.getByCode(code).id);\n }\n\n /** Converts stored enum ids to codes, defaulting an unset entity field to an empty array. */\n mapFromDb(value: unknown): unknown[] {\n return (value as readonly unknown[] | null | undefined)?.map((id) => this.enumObject.findById(id).code) || [];\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : value;\n }\n}\n\n/** Similar to SimpleSerde, but applies the superstruct `assert` function when reading values from the db. */\nexport class SuperstructSerde implements ScalarCodec {\n dbType = \"jsonb\";\n isArray = false;\n\n // Use a dynamic require so that downstream projects don't have to depend on superstruct\n // until they want to, i.e. we don't have superstruct in the joist-orm package.json.\n private assert = runtimeRequire(\"superstruct\").assert;\n\n constructor(private superstruct: any) {}\n\n /** JSON schemas describe a jsonb value, even when that value is a JSON array. */\n get outputType(): TypeInfo | undefined {\n return this.constructor === SuperstructSerde && !this.isArray\n ? { dbType: canonicalDbType(this.dbType), domain: this.superstruct, arrayElementSafe: true }\n : undefined;\n }\n\n /** Serializes the domain JSON value without running the read-time schema validation. */\n mapToDbValue(value: unknown): unknown {\n return JSON.stringify(value);\n }\n\n // JSON is returned by postgres already parsed, so we should just be able to return our data directly. Unlike with\n // JsonSerde, we can assume that superstruct would have parsed any complex types in the json correctly so we don't\n // need to do a round trip through JSON.stringify.\n mapToRow(value: any): any {\n return value;\n }\n\n mapToDb(value: any) {\n return JSON.stringify(value);\n }\n\n /** Validates a driver JSON value with the field's Superstruct schema. */\n mapFromDb(value: unknown): unknown {\n const dbValue = maybeNullToUndefined(value);\n if (dbValue) this.assert(dbValue, this.superstruct);\n return dbValue;\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : value;\n }\n}\n\nexport class JsonSerde implements ScalarCodec {\n dbType = \"jsonb\";\n isArray = false;\n\n constructor() {}\n\n /** Unvalidated JSON uses its own encoder, not a primitive or schema-specific codec. */\n get outputType(): TypeInfo | undefined {\n return this.constructor === JsonSerde && !this.isArray\n ? { dbType: canonicalDbType(this.dbType), domain: JsonSerde, arrayElementSafe: true }\n : undefined;\n }\n\n /** Serializes the whole JSON value, including JSON arrays and custom toJSON methods. */\n mapToDbValue(value: unknown): unknown {\n return JSON.stringify(value);\n }\n\n // JSON is returned by postgres already parsed, so if we are trying to recreate then we need to stringify, then parse.\n // It's necessary to do this instead of just returning the object directly because any complex types in the json need\n // to be stringified to correctly reflect the db value.\n mapToRow(value: any): any {\n const json = JSON.stringify(value);\n return isDefined(json) ? JSON.parse(json) : undefined;\n }\n\n mapToDb(value: any) {\n return JSON.stringify(value);\n }\n\n /** Keeps the driver's parsed JSON value, normalizing SQL NULL for entity fields. */\n mapFromDb(value: unknown): unknown {\n return maybeNullToUndefined(value);\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : value;\n }\n}\n\n/** Similar to SimpleSerde, but applies the zod's `parse` function when reading values from the db. */\nexport class ZodSerde implements ScalarCodec {\n dbType = \"jsonb\";\n isArray = false;\n\n constructor(private zodSchema: any) {}\n\n /** Parsing and transformations are compatible only for the same schema object. */\n get outputType(): TypeInfo | undefined {\n return this.constructor === ZodSerde && !this.isArray\n ? { dbType: canonicalDbType(this.dbType), domain: this.zodSchema, arrayElementSafe: true }\n : undefined;\n }\n\n /** Serializes the domain JSON value without running the read-time Zod parser or transforms. */\n mapToDbValue(value: unknown): unknown {\n return JSON.stringify(value);\n }\n\n // JSON is returned by postgres already parsed, so we should just be able to return our data directly. Unlike with\n // JsonSerde, we can assume that zod would have parsed any complex types in the json correctly so we don't need to\n // do a round trip through JSON.stringify.\n mapToRow(value: any): any {\n return value;\n }\n\n mapToDb(value: any) {\n return JSON.stringify(value);\n }\n\n /** Parses a driver JSON value with the field's Zod schema. */\n mapFromDb(value: unknown): unknown {\n const dbValue = maybeNullToUndefined(value);\n return dbValue ? this.zodSchema.parse(dbValue) : dbValue;\n }\n\n mapFromJsonAgg(value: any): any {\n return value === null ? value : value;\n }\n}\n\n/** PostgreSQL does not enforce declared array dimensions; reject nested values rather than coercing subarrays. */\nfunction assertFlatNumericArray(value: unknown): void {\n if (Array.isArray(value) && value.some(Array.isArray)) {\n throw new Error(\"Native numeric arrays must be one-dimensional\");\n }\n}\n\n/** Only driver-native numbers agree with Number aggregates; int8/numeric identity values do not. */\nfunction primitiveOutputDomain(dbType: string): unknown {\n switch (dbType) {\n case \"int2\":\n case \"int4\":\n case \"float4\":\n case \"float8\":\n return Number;\n case \"text\":\n case \"varchar\":\n case \"bpchar\":\n return String;\n default:\n return PrimitiveSerde;\n }\n}\n\n/** Normalizes SQL NULL to an unset entity field. */\nfunction maybeNullToUndefined(value: any): any {\n return value === null ? undefined : value;\n}\n"],"mappings":"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"}
|