@metaobjectsdev/runtime-ts 0.23.1-rc.1 → 0.23.2
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/dist/query-builder.d.ts.map +1 -1
- package/dist/query-builder.js +32 -9
- package/dist/query-builder.js.map +1 -1
- package/dist/type-coercer.d.ts +17 -0
- package/dist/type-coercer.d.ts.map +1 -1
- package/dist/type-coercer.js +109 -5
- package/dist/type-coercer.js.map +1 -1
- package/package.json +3 -3
- package/src/query-builder.ts +33 -9
- package/src/type-coercer.ts +103 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-builder.d.ts","sourceRoot":"","sources":["../src/query-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"query-builder.d.ts","sourceRoot":"","sources":["../src/query-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAU/E,OAAO,KAAK,EACV,WAAW,EAAW,cAAc,EAAE,GAAG,EACzC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAC1D,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAEtE,MAAM,MAAM,WAAW,GACnB,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAChC,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GACnB;IACE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEN,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,EAAE,CAc1D;AA+BD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,oBAAqD,GAC9D,WAAW,GAAG,IAAI,CAoBpB;AAyED,2FAA2F;AAC3F,wBAAgB,eAAe,CAC7B,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,SAAS,EACf,eAAe,CAAC,EAAE,MAAM,EAAE,EAC1B,QAAQ,GAAE,oBAAqD,GAC9D,UAAU,CAmBZ;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAC5C,QAAQ,GAAE,oBAAqD,GAC9D,SAAS,CAKX;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAC3B,QAAQ,GAAE,oBAAqD,GAC9D,UAAU,CAQZ;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,cAAc,CAAC;CACvB;AAaD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EACxC,QAAQ,GAAE,oBAAqD,EAC/D,KAAK,CAAC,EAAE,kBAAkB,GACzB,UAAU,CAeZ;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAC7B,QAAQ,GAAE,oBAAqD,EAC/D,KAAK,CAAC,EAAE,kBAAkB,GACzB,UAAU,CAYZ"}
|
package/dist/query-builder.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TYPE_FIELD, TYPE_IDENTITY, IDENTITY_SUBTYPE_PRIMARY, IDENTITY_ATTR_FIELDS, DEFAULT_COLUMN_NAMING_STRATEGY, resolveTableName, resolveColumnName, } from "@metaobjectsdev/metadata";
|
|
2
2
|
import { MetadataError } from "./errors.js";
|
|
3
|
+
import { intValueMapOf } from "./type-coercer.js";
|
|
3
4
|
export { resolveTableName } from "@metaobjectsdev/metadata";
|
|
4
5
|
export function resolvePkFields(entity) {
|
|
5
6
|
// Effective children (own + inherited via super) so a TPH subtype resolves
|
|
@@ -68,13 +69,20 @@ export function compileFilter(entity, filter, strategy = DEFAULT_COLUMN_NAMING_S
|
|
|
68
69
|
function compileEntry(entity, fieldName, value, strategy) {
|
|
69
70
|
const field = getField(entity, fieldName);
|
|
70
71
|
const column = resolveColumnName(field, strategy);
|
|
72
|
+
// An int-backed field.enum (@intValueMap) stores an INTEGER while its filter value
|
|
73
|
+
// is the member SYMBOL, so every comparison value must be encoded before it is
|
|
74
|
+
// bound — otherwise Postgres rejects the statement outright ("invalid input syntax
|
|
75
|
+
// for type integer"). Applied at this ONE seam because every operator arrives here
|
|
76
|
+
// with the field in hand; `like` is not reachable for such a field (the loader's
|
|
77
|
+
// field-level operator band drops it), and `isNull` carries no value to encode.
|
|
78
|
+
const enc = (v) => encodeFilterValue(field, v);
|
|
71
79
|
if (value === null)
|
|
72
80
|
return { kind: "isNull", column, not: false };
|
|
73
81
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
74
|
-
return { kind: "eq", column, value };
|
|
82
|
+
return { kind: "eq", column, value: enc(value) };
|
|
75
83
|
}
|
|
76
84
|
if (Array.isArray(value)) {
|
|
77
|
-
return { kind: "in", column, values: value };
|
|
85
|
+
return { kind: "in", column, values: value.map(enc) };
|
|
78
86
|
}
|
|
79
87
|
// If multiple operators are set on one field ({ $gte: 5, $lte: 10 }), the FIRST match wins
|
|
80
88
|
// and later operators are silently ignored. Range queries must use $and: [...] explicitly.
|
|
@@ -82,30 +90,45 @@ function compileEntry(entity, fieldName, value, strategy) {
|
|
|
82
90
|
if ("$eq" in op && op.$eq !== undefined) {
|
|
83
91
|
if (op.$eq === null)
|
|
84
92
|
return { kind: "isNull", column, not: false };
|
|
85
|
-
return { kind: "eq", column, value: op.$eq };
|
|
93
|
+
return { kind: "eq", column, value: enc(op.$eq) };
|
|
86
94
|
}
|
|
87
95
|
if ("$ne" in op && op.$ne !== undefined) {
|
|
88
96
|
if (op.$ne === null)
|
|
89
97
|
return { kind: "isNull", column, not: true };
|
|
90
|
-
return { kind: "ne", column, value: op.$ne };
|
|
98
|
+
return { kind: "ne", column, value: enc(op.$ne) };
|
|
91
99
|
}
|
|
92
100
|
if ("$gt" in op && op.$gt !== undefined)
|
|
93
|
-
return { kind: "gt", column, value: op.$gt };
|
|
101
|
+
return { kind: "gt", column, value: enc(op.$gt) };
|
|
94
102
|
if ("$gte" in op && op.$gte !== undefined)
|
|
95
|
-
return { kind: "gte", column, value: op.$gte };
|
|
103
|
+
return { kind: "gte", column, value: enc(op.$gte) };
|
|
96
104
|
if ("$lt" in op && op.$lt !== undefined)
|
|
97
|
-
return { kind: "lt", column, value: op.$lt };
|
|
105
|
+
return { kind: "lt", column, value: enc(op.$lt) };
|
|
98
106
|
if ("$lte" in op && op.$lte !== undefined)
|
|
99
|
-
return { kind: "lte", column, value: op.$lte };
|
|
107
|
+
return { kind: "lte", column, value: enc(op.$lte) };
|
|
100
108
|
if ("$like" in op && op.$like !== undefined)
|
|
101
109
|
return { kind: "like", column, pattern: op.$like };
|
|
102
110
|
if ("$in" in op && op.$in !== undefined)
|
|
103
|
-
return { kind: "in", column, values: op.$in };
|
|
111
|
+
return { kind: "in", column, values: op.$in.map(enc) };
|
|
104
112
|
if ("$isNull" in op && op.$isNull !== undefined) {
|
|
105
113
|
return { kind: "isNull", column, not: !op.$isNull };
|
|
106
114
|
}
|
|
107
115
|
throw new MetadataError(`No recognized operator on filter for field '${fieldName}'`);
|
|
108
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Encode one filter comparison value for an int-backed `field.enum`: the member
|
|
119
|
+
* SYMBOL becomes its declared integer. Anything else — a string-backed enum, a
|
|
120
|
+
* non-string value, or a symbol with no mapping — passes through untouched. An
|
|
121
|
+
* unmapped symbol is deliberately NOT rejected here: a filter that matches nothing
|
|
122
|
+
* is the honest answer for a member that does not exist, and the loader already
|
|
123
|
+
* pins `@intValueMap`'s key set to `@values`.
|
|
124
|
+
*/
|
|
125
|
+
function encodeFilterValue(field, value) {
|
|
126
|
+
const intMap = intValueMapOf(field);
|
|
127
|
+
if (intMap === undefined || typeof value !== "string")
|
|
128
|
+
return value;
|
|
129
|
+
const stored = intMap[value];
|
|
130
|
+
return (typeof stored === "number" ? stored : value);
|
|
131
|
+
}
|
|
109
132
|
function normalizeOrderBy(input, entity, strategy) {
|
|
110
133
|
if (input === undefined)
|
|
111
134
|
return undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-builder.js","sourceRoot":"","sources":["../src/query-builder.ts"],"names":[],"mappings":"AACA,OAAO,EACL,UAAU,EAAE,aAAa,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,8BAA8B,EAC9B,gBAAgB,EAAE,iBAAiB,GACpC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"query-builder.js","sourceRoot":"","sources":["../src/query-builder.ts"],"names":[],"mappings":"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"}
|
package/dist/type-coercer.d.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import type { MetaData } from "@metaobjectsdev/metadata";
|
|
2
2
|
import type { Dialect, Row } from "./persistence-driver.js";
|
|
3
3
|
export declare function coerceRowOnRead(entity: MetaData, row: Row, dialect: Dialect): Row;
|
|
4
|
+
/**
|
|
5
|
+
* The effective `@intValueMap` (member symbol → integer) for an int-backed
|
|
6
|
+
* `field.enum`, or undefined when the enum is string-backed. Its PRESENCE is the
|
|
7
|
+
* whole trigger for integer persistence (design D5) — there is no separate flag.
|
|
8
|
+
*
|
|
9
|
+
* ADR-0039: RESOLVING (`attr`, not `ownAttr`), and load-bearing rather than
|
|
10
|
+
* incidental. Post-#246 an own `@intValueMap` against a shared (root-level
|
|
11
|
+
* abstract) enum is `ERR_ENUM_EXTENDS_VALUES_CONFLICT`, so the map lives on the
|
|
12
|
+
* SHARED DECLARATION and every consuming field INHERITS it — an own-only read
|
|
13
|
+
* would see undefined on exactly the shape adopters are steered toward and bind
|
|
14
|
+
* the symbol straight into an integer column.
|
|
15
|
+
*
|
|
16
|
+
* Mirrors `codegen-ts`'s `intValueMapOf`; duplicated rather than shared because
|
|
17
|
+
* `runtime-ts` must not depend on a codegen package (the two are disjoint trees,
|
|
18
|
+
* and only one of them ships to a server at runtime).
|
|
19
|
+
*/
|
|
20
|
+
export declare function intValueMapOf(field: MetaData): Record<string, number> | undefined;
|
|
4
21
|
export declare function coerceRowOnWrite(entity: MetaData, row: Row, dialect: Dialect): Row;
|
|
5
22
|
//# sourceMappingURL=type-coercer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-coercer.d.ts","sourceRoot":"","sources":["../src/type-coercer.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"type-coercer.d.ts","sourceRoot":"","sources":["../src/type-coercer.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAazD,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAE5D,wBAAgB,eAAe,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAKjF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAKjF;AAuED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAQlF"}
|
package/dist/type-coercer.js
CHANGED
|
@@ -12,18 +12,122 @@
|
|
|
12
12
|
// Date/timestamp coercion (ISO string ↔ Date) is deferred: pg accepts ISO
|
|
13
13
|
// strings for temporal columns directly, and the temporal read parsers pin the
|
|
14
14
|
// wire form.
|
|
15
|
-
import { TYPE_FIELD, FIELD_SUBTYPE_BOOLEAN, FIELD_SUBTYPE_OBJECT, FIELD_ATTR_STORAGE, FIELD_ATTR_DB_COLUMN_TYPE, STORAGE_JSONB, STORAGE_FLATTENED, DB_COLUMN_TYPE_JSONB, } from "@metaobjectsdev/metadata";
|
|
15
|
+
import { TYPE_FIELD, FIELD_SUBTYPE_BOOLEAN, FIELD_SUBTYPE_ENUM, FIELD_SUBTYPE_OBJECT, FIELD_ATTR_STORAGE, FIELD_ATTR_DB_COLUMN_TYPE, FIELD_ATTR_INT_VALUE_MAP, STORAGE_JSONB, STORAGE_FLATTENED, DB_COLUMN_TYPE_JSONB, } from "@metaobjectsdev/metadata";
|
|
16
16
|
export function coerceRowOnRead(entity, row, dialect) {
|
|
17
17
|
const hydrated = deserializeJsonbObjectFields(entity, row);
|
|
18
|
+
const decoded = decodeIntBackedEnums(entity, hydrated);
|
|
18
19
|
if (dialect !== "sqlite")
|
|
19
|
-
return
|
|
20
|
-
return mapBooleansFromInt(entity,
|
|
20
|
+
return decoded;
|
|
21
|
+
return mapBooleansFromInt(entity, decoded);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The effective `@intValueMap` (member symbol → integer) for an int-backed
|
|
25
|
+
* `field.enum`, or undefined when the enum is string-backed. Its PRESENCE is the
|
|
26
|
+
* whole trigger for integer persistence (design D5) — there is no separate flag.
|
|
27
|
+
*
|
|
28
|
+
* ADR-0039: RESOLVING (`attr`, not `ownAttr`), and load-bearing rather than
|
|
29
|
+
* incidental. Post-#246 an own `@intValueMap` against a shared (root-level
|
|
30
|
+
* abstract) enum is `ERR_ENUM_EXTENDS_VALUES_CONFLICT`, so the map lives on the
|
|
31
|
+
* SHARED DECLARATION and every consuming field INHERITS it — an own-only read
|
|
32
|
+
* would see undefined on exactly the shape adopters are steered toward and bind
|
|
33
|
+
* the symbol straight into an integer column.
|
|
34
|
+
*
|
|
35
|
+
* Mirrors `codegen-ts`'s `intValueMapOf`; duplicated rather than shared because
|
|
36
|
+
* `runtime-ts` must not depend on a codegen package (the two are disjoint trees,
|
|
37
|
+
* and only one of them ships to a server at runtime).
|
|
38
|
+
*/
|
|
39
|
+
export function intValueMapOf(field) {
|
|
40
|
+
if (field.subType !== FIELD_SUBTYPE_ENUM)
|
|
41
|
+
return undefined;
|
|
42
|
+
const raw = field.attr(FIELD_ATTR_INT_VALUE_MAP);
|
|
43
|
+
if (raw === undefined || raw === null || typeof raw !== "object")
|
|
44
|
+
return undefined;
|
|
45
|
+
return raw;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Encode a member SYMBOL to its declared integer for an int-backed `field.enum`.
|
|
49
|
+
*
|
|
50
|
+
* An UNMAPPED symbol is passed through untouched rather than rejected here: the
|
|
51
|
+
* column's `CHECK` is what enforces membership, and inventing a value would hide
|
|
52
|
+
* the drift. Matching Python's write codec exactly — every port leaves the write
|
|
53
|
+
* side to the database.
|
|
54
|
+
*/
|
|
55
|
+
function encodeIntBackedEnum(intMap, value) {
|
|
56
|
+
if (typeof value !== "string")
|
|
57
|
+
return value;
|
|
58
|
+
const stored = intMap[value];
|
|
59
|
+
return typeof stored === "number" ? stored : value;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Decode the stored integer back to its member symbol, so the runtime's return
|
|
63
|
+
* value is the symbol in both backing modes — int-backing is a persistence-layer
|
|
64
|
+
* concern and must be invisible above this codec (ADR-0019).
|
|
65
|
+
*
|
|
66
|
+
* An int with no member THROWS. The row holds data the model says is impossible
|
|
67
|
+
* (a hand-written INSERT, or a member removed without a migration); surfacing the
|
|
68
|
+
* raw integer would hand the caller a "member" that is not one, and returning
|
|
69
|
+
* null would hide the corruption behind a nullable column. Every port throws
|
|
70
|
+
* here — the generated Drizzle `customType`'s `fromDriver` included.
|
|
71
|
+
*/
|
|
72
|
+
function decodeIntBackedEnums(entity, row) {
|
|
73
|
+
let out = null;
|
|
74
|
+
// ADR-0039: resolving — an int-backed enum may be inherited from a base via extends.
|
|
75
|
+
for (const child of entity.children()) {
|
|
76
|
+
if (child.type !== TYPE_FIELD)
|
|
77
|
+
continue;
|
|
78
|
+
const intMap = intValueMapOf(child);
|
|
79
|
+
if (intMap === undefined)
|
|
80
|
+
continue;
|
|
81
|
+
if (!(child.name in row))
|
|
82
|
+
continue;
|
|
83
|
+
const raw = row[child.name];
|
|
84
|
+
if (raw === null || raw === undefined)
|
|
85
|
+
continue;
|
|
86
|
+
// A driver may hand back a BIGINT-ish string; Number() covers both shapes.
|
|
87
|
+
const stored = typeof raw === "number" ? raw : Number(raw);
|
|
88
|
+
if (!Number.isInteger(stored))
|
|
89
|
+
continue;
|
|
90
|
+
const member = Object.keys(intMap).find((k) => intMap[k] === stored);
|
|
91
|
+
if (member === undefined) {
|
|
92
|
+
throw new Error(`field.enum '${child.name}' read stored value ${stored} with no member in ` +
|
|
93
|
+
`@intValueMap (declared: ${JSON.stringify(intMap)}) — the database holds a ` +
|
|
94
|
+
`value the model does not describe.`);
|
|
95
|
+
}
|
|
96
|
+
out ??= { ...row };
|
|
97
|
+
out[child.name] = member;
|
|
98
|
+
}
|
|
99
|
+
return out ?? row;
|
|
100
|
+
}
|
|
101
|
+
function encodeIntBackedEnums(entity, row) {
|
|
102
|
+
let out = null;
|
|
103
|
+
for (const child of entity.children()) {
|
|
104
|
+
if (child.type !== TYPE_FIELD)
|
|
105
|
+
continue;
|
|
106
|
+
const intMap = intValueMapOf(child);
|
|
107
|
+
if (intMap === undefined)
|
|
108
|
+
continue;
|
|
109
|
+
if (!(child.name in row))
|
|
110
|
+
continue;
|
|
111
|
+
const raw = row[child.name];
|
|
112
|
+
if (raw === null || raw === undefined)
|
|
113
|
+
continue;
|
|
114
|
+
const encoded = encodeIntBackedEnum(intMap, raw);
|
|
115
|
+
if (encoded === raw)
|
|
116
|
+
continue;
|
|
117
|
+
out ??= { ...row };
|
|
118
|
+
out[child.name] = encoded;
|
|
119
|
+
}
|
|
120
|
+
return out ?? row;
|
|
21
121
|
}
|
|
22
122
|
export function coerceRowOnWrite(entity, row, dialect) {
|
|
23
123
|
const jsonbColumned = serializeJsonbColumns(entity, row);
|
|
124
|
+
// Dialect-independent: an int-backed enum's column is INTEGER on every dialect
|
|
125
|
+
// (SQLite has one integer storage class), so the symbol→int encode is not gated
|
|
126
|
+
// on the dialect the way the boolean mapping below is.
|
|
127
|
+
const encoded = encodeIntBackedEnums(entity, jsonbColumned);
|
|
24
128
|
if (dialect !== "sqlite")
|
|
25
|
-
return
|
|
26
|
-
return mapBooleansToInt(entity,
|
|
129
|
+
return encoded;
|
|
130
|
+
return mapBooleansToInt(entity, encoded);
|
|
27
131
|
}
|
|
28
132
|
/**
|
|
29
133
|
* A `field.object` lands in a single JSONB column unless its `@storage` is
|
package/dist/type-coercer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-coercer.js","sourceRoot":"","sources":["../src/type-coercer.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,8EAA8E;AAC9E,0DAA0D;AAC1D,yEAAyE;AACzE,8EAA8E;AAC9E,6EAA6E;AAC7E,iFAAiF;AACjF,gFAAgF;AAChF,2EAA2E;AAC3E,EAAE;AACF,0EAA0E;AAC1E,+EAA+E;AAC/E,aAAa;AAGb,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,yBAAyB,EACzB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAGlC,MAAM,UAAU,eAAe,CAAC,MAAgB,EAAE,GAAQ,EAAE,OAAgB;IAC1E,MAAM,QAAQ,GAAG,4BAA4B,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3D,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"type-coercer.js","sourceRoot":"","sources":["../src/type-coercer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metaobjectsdev/runtime-ts",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.2",
|
|
4
4
|
"description": "Node-side runtime helpers for MetaObjects: Fastify route builders, Drizzle filter/sort integration, Kysely drivers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"access": "public"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@metaobjectsdev/metadata": "0.23.
|
|
68
|
-
"@metaobjectsdev/render": "0.23.
|
|
67
|
+
"@metaobjectsdev/metadata": "0.23.2",
|
|
68
|
+
"@metaobjectsdev/render": "0.23.2",
|
|
69
69
|
"qs": "^6.13.0"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
package/src/query-builder.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
resolveTableName, resolveColumnName,
|
|
8
8
|
} from "@metaobjectsdev/metadata";
|
|
9
9
|
import { MetadataError } from "./errors.js";
|
|
10
|
+
import { intValueMapOf } from "./type-coercer.js";
|
|
10
11
|
import type {
|
|
11
12
|
WhereClause, OrderBy, PrimitiveValue, Row,
|
|
12
13
|
SelectSpec, InsertSpec, UpdateSpec, DeleteSpec, CountSpec,
|
|
@@ -118,12 +119,20 @@ function compileEntry(
|
|
|
118
119
|
const field = getField(entity, fieldName);
|
|
119
120
|
const column = resolveColumnName(field, strategy);
|
|
120
121
|
|
|
122
|
+
// An int-backed field.enum (@intValueMap) stores an INTEGER while its filter value
|
|
123
|
+
// is the member SYMBOL, so every comparison value must be encoded before it is
|
|
124
|
+
// bound — otherwise Postgres rejects the statement outright ("invalid input syntax
|
|
125
|
+
// for type integer"). Applied at this ONE seam because every operator arrives here
|
|
126
|
+
// with the field in hand; `like` is not reachable for such a field (the loader's
|
|
127
|
+
// field-level operator band drops it), and `isNull` carries no value to encode.
|
|
128
|
+
const enc = <T,>(v: T): T => encodeFilterValue(field, v);
|
|
129
|
+
|
|
121
130
|
if (value === null) return { kind: "isNull", column, not: false };
|
|
122
131
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
123
|
-
return { kind: "eq", column, value };
|
|
132
|
+
return { kind: "eq", column, value: enc(value) };
|
|
124
133
|
}
|
|
125
134
|
if (Array.isArray(value)) {
|
|
126
|
-
return { kind: "in", column, values: value as PrimitiveValue[] };
|
|
135
|
+
return { kind: "in", column, values: (value as PrimitiveValue[]).map(enc) };
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
// If multiple operators are set on one field ({ $gte: 5, $lte: 10 }), the FIRST match wins
|
|
@@ -131,24 +140,39 @@ function compileEntry(
|
|
|
131
140
|
const op = value;
|
|
132
141
|
if ("$eq" in op && op.$eq !== undefined) {
|
|
133
142
|
if (op.$eq === null) return { kind: "isNull", column, not: false };
|
|
134
|
-
return { kind: "eq", column, value: op.$eq };
|
|
143
|
+
return { kind: "eq", column, value: enc(op.$eq) };
|
|
135
144
|
}
|
|
136
145
|
if ("$ne" in op && op.$ne !== undefined) {
|
|
137
146
|
if (op.$ne === null) return { kind: "isNull", column, not: true };
|
|
138
|
-
return { kind: "ne", column, value: op.$ne };
|
|
147
|
+
return { kind: "ne", column, value: enc(op.$ne) };
|
|
139
148
|
}
|
|
140
|
-
if ("$gt" in op && op.$gt !== undefined) return { kind: "gt", column, value: op.$gt };
|
|
141
|
-
if ("$gte" in op && op.$gte !== undefined) return { kind: "gte", column, value: op.$gte };
|
|
142
|
-
if ("$lt" in op && op.$lt !== undefined) return { kind: "lt", column, value: op.$lt };
|
|
143
|
-
if ("$lte" in op && op.$lte !== undefined) return { kind: "lte", column, value: op.$lte };
|
|
149
|
+
if ("$gt" in op && op.$gt !== undefined) return { kind: "gt", column, value: enc(op.$gt) };
|
|
150
|
+
if ("$gte" in op && op.$gte !== undefined) return { kind: "gte", column, value: enc(op.$gte) };
|
|
151
|
+
if ("$lt" in op && op.$lt !== undefined) return { kind: "lt", column, value: enc(op.$lt) };
|
|
152
|
+
if ("$lte" in op && op.$lte !== undefined) return { kind: "lte", column, value: enc(op.$lte) };
|
|
144
153
|
if ("$like" in op && op.$like !== undefined) return { kind: "like", column, pattern: op.$like };
|
|
145
|
-
if ("$in" in op && op.$in !== undefined) return { kind: "in", column, values: op.$in };
|
|
154
|
+
if ("$in" in op && op.$in !== undefined) return { kind: "in", column, values: op.$in.map(enc) };
|
|
146
155
|
if ("$isNull" in op && op.$isNull !== undefined) {
|
|
147
156
|
return { kind: "isNull", column, not: !op.$isNull };
|
|
148
157
|
}
|
|
149
158
|
throw new MetadataError(`No recognized operator on filter for field '${fieldName}'`);
|
|
150
159
|
}
|
|
151
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Encode one filter comparison value for an int-backed `field.enum`: the member
|
|
163
|
+
* SYMBOL becomes its declared integer. Anything else — a string-backed enum, a
|
|
164
|
+
* non-string value, or a symbol with no mapping — passes through untouched. An
|
|
165
|
+
* unmapped symbol is deliberately NOT rejected here: a filter that matches nothing
|
|
166
|
+
* is the honest answer for a member that does not exist, and the loader already
|
|
167
|
+
* pins `@intValueMap`'s key set to `@values`.
|
|
168
|
+
*/
|
|
169
|
+
function encodeFilterValue<T>(field: MetaData, value: T): T {
|
|
170
|
+
const intMap = intValueMapOf(field);
|
|
171
|
+
if (intMap === undefined || typeof value !== "string") return value;
|
|
172
|
+
const stored = intMap[value];
|
|
173
|
+
return (typeof stored === "number" ? stored : value) as T;
|
|
174
|
+
}
|
|
175
|
+
|
|
152
176
|
function normalizeOrderBy(
|
|
153
177
|
input: QueryOpts["orderBy"], entity: MetaData, strategy: ColumnNamingStrategy,
|
|
154
178
|
): OrderBy[] | undefined {
|
package/src/type-coercer.ts
CHANGED
|
@@ -17,9 +17,11 @@ import type { MetaData } from "@metaobjectsdev/metadata";
|
|
|
17
17
|
import {
|
|
18
18
|
TYPE_FIELD,
|
|
19
19
|
FIELD_SUBTYPE_BOOLEAN,
|
|
20
|
+
FIELD_SUBTYPE_ENUM,
|
|
20
21
|
FIELD_SUBTYPE_OBJECT,
|
|
21
22
|
FIELD_ATTR_STORAGE,
|
|
22
23
|
FIELD_ATTR_DB_COLUMN_TYPE,
|
|
24
|
+
FIELD_ATTR_INT_VALUE_MAP,
|
|
23
25
|
STORAGE_JSONB,
|
|
24
26
|
STORAGE_FLATTENED,
|
|
25
27
|
DB_COLUMN_TYPE_JSONB,
|
|
@@ -28,14 +30,111 @@ import type { Dialect, Row } from "./persistence-driver.js";
|
|
|
28
30
|
|
|
29
31
|
export function coerceRowOnRead(entity: MetaData, row: Row, dialect: Dialect): Row {
|
|
30
32
|
const hydrated = deserializeJsonbObjectFields(entity, row);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
const decoded = decodeIntBackedEnums(entity, hydrated);
|
|
34
|
+
if (dialect !== "sqlite") return decoded;
|
|
35
|
+
return mapBooleansFromInt(entity, decoded);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The effective `@intValueMap` (member symbol → integer) for an int-backed
|
|
40
|
+
* `field.enum`, or undefined when the enum is string-backed. Its PRESENCE is the
|
|
41
|
+
* whole trigger for integer persistence (design D5) — there is no separate flag.
|
|
42
|
+
*
|
|
43
|
+
* ADR-0039: RESOLVING (`attr`, not `ownAttr`), and load-bearing rather than
|
|
44
|
+
* incidental. Post-#246 an own `@intValueMap` against a shared (root-level
|
|
45
|
+
* abstract) enum is `ERR_ENUM_EXTENDS_VALUES_CONFLICT`, so the map lives on the
|
|
46
|
+
* SHARED DECLARATION and every consuming field INHERITS it — an own-only read
|
|
47
|
+
* would see undefined on exactly the shape adopters are steered toward and bind
|
|
48
|
+
* the symbol straight into an integer column.
|
|
49
|
+
*
|
|
50
|
+
* Mirrors `codegen-ts`'s `intValueMapOf`; duplicated rather than shared because
|
|
51
|
+
* `runtime-ts` must not depend on a codegen package (the two are disjoint trees,
|
|
52
|
+
* and only one of them ships to a server at runtime).
|
|
53
|
+
*/
|
|
54
|
+
export function intValueMapOf(field: MetaData): Record<string, number> | undefined {
|
|
55
|
+
if (field.subType !== FIELD_SUBTYPE_ENUM) return undefined;
|
|
56
|
+
const raw = field.attr(FIELD_ATTR_INT_VALUE_MAP);
|
|
57
|
+
if (raw === undefined || raw === null || typeof raw !== "object") return undefined;
|
|
58
|
+
return raw as Record<string, number>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Encode a member SYMBOL to its declared integer for an int-backed `field.enum`.
|
|
63
|
+
*
|
|
64
|
+
* An UNMAPPED symbol is passed through untouched rather than rejected here: the
|
|
65
|
+
* column's `CHECK` is what enforces membership, and inventing a value would hide
|
|
66
|
+
* the drift. Matching Python's write codec exactly — every port leaves the write
|
|
67
|
+
* side to the database.
|
|
68
|
+
*/
|
|
69
|
+
function encodeIntBackedEnum(intMap: Record<string, number>, value: unknown): unknown {
|
|
70
|
+
if (typeof value !== "string") return value;
|
|
71
|
+
const stored = intMap[value];
|
|
72
|
+
return typeof stored === "number" ? stored : value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Decode the stored integer back to its member symbol, so the runtime's return
|
|
77
|
+
* value is the symbol in both backing modes — int-backing is a persistence-layer
|
|
78
|
+
* concern and must be invisible above this codec (ADR-0019).
|
|
79
|
+
*
|
|
80
|
+
* An int with no member THROWS. The row holds data the model says is impossible
|
|
81
|
+
* (a hand-written INSERT, or a member removed without a migration); surfacing the
|
|
82
|
+
* raw integer would hand the caller a "member" that is not one, and returning
|
|
83
|
+
* null would hide the corruption behind a nullable column. Every port throws
|
|
84
|
+
* here — the generated Drizzle `customType`'s `fromDriver` included.
|
|
85
|
+
*/
|
|
86
|
+
function decodeIntBackedEnums(entity: MetaData, row: Row): Row {
|
|
87
|
+
let out: Row | null = null;
|
|
88
|
+
// ADR-0039: resolving — an int-backed enum may be inherited from a base via extends.
|
|
89
|
+
for (const child of entity.children()) {
|
|
90
|
+
if (child.type !== TYPE_FIELD) continue;
|
|
91
|
+
const intMap = intValueMapOf(child);
|
|
92
|
+
if (intMap === undefined) continue;
|
|
93
|
+
if (!(child.name in row)) continue;
|
|
94
|
+
const raw = row[child.name];
|
|
95
|
+
if (raw === null || raw === undefined) continue;
|
|
96
|
+
// A driver may hand back a BIGINT-ish string; Number() covers both shapes.
|
|
97
|
+
const stored = typeof raw === "number" ? raw : Number(raw);
|
|
98
|
+
if (!Number.isInteger(stored)) continue;
|
|
99
|
+
const member = Object.keys(intMap).find((k) => intMap[k] === stored);
|
|
100
|
+
if (member === undefined) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`field.enum '${child.name}' read stored value ${stored} with no member in ` +
|
|
103
|
+
`@intValueMap (declared: ${JSON.stringify(intMap)}) — the database holds a ` +
|
|
104
|
+
`value the model does not describe.`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
out ??= { ...row };
|
|
108
|
+
out[child.name] = member;
|
|
109
|
+
}
|
|
110
|
+
return out ?? row;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function encodeIntBackedEnums(entity: MetaData, row: Row): Row {
|
|
114
|
+
let out: Row | null = null;
|
|
115
|
+
for (const child of entity.children()) {
|
|
116
|
+
if (child.type !== TYPE_FIELD) continue;
|
|
117
|
+
const intMap = intValueMapOf(child);
|
|
118
|
+
if (intMap === undefined) continue;
|
|
119
|
+
if (!(child.name in row)) continue;
|
|
120
|
+
const raw = row[child.name];
|
|
121
|
+
if (raw === null || raw === undefined) continue;
|
|
122
|
+
const encoded = encodeIntBackedEnum(intMap, raw);
|
|
123
|
+
if (encoded === raw) continue;
|
|
124
|
+
out ??= { ...row };
|
|
125
|
+
out[child.name] = encoded;
|
|
126
|
+
}
|
|
127
|
+
return out ?? row;
|
|
33
128
|
}
|
|
34
129
|
|
|
35
130
|
export function coerceRowOnWrite(entity: MetaData, row: Row, dialect: Dialect): Row {
|
|
36
131
|
const jsonbColumned = serializeJsonbColumns(entity, row);
|
|
37
|
-
|
|
38
|
-
|
|
132
|
+
// Dialect-independent: an int-backed enum's column is INTEGER on every dialect
|
|
133
|
+
// (SQLite has one integer storage class), so the symbol→int encode is not gated
|
|
134
|
+
// on the dialect the way the boolean mapping below is.
|
|
135
|
+
const encoded = encodeIntBackedEnums(entity, jsonbColumned);
|
|
136
|
+
if (dialect !== "sqlite") return encoded;
|
|
137
|
+
return mapBooleansToInt(entity, encoded);
|
|
39
138
|
}
|
|
40
139
|
|
|
41
140
|
/**
|