@metaobjectsdev/runtime-ts 0.6.0 → 0.7.0-rc.10
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/README.md +5 -3
- package/dist/hono/index.d.ts +44 -0
- package/dist/hono/index.d.ts.map +1 -0
- package/dist/hono/index.js +188 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/hono/mount-read-only.d.ts +18 -0
- package/dist/hono/mount-read-only.d.ts.map +1 -0
- package/dist/hono/mount-read-only.js +151 -0
- package/dist/hono/mount-read-only.js.map +1 -0
- package/dist/n2m-resolver.d.ts +4 -4
- package/dist/n2m-resolver.d.ts.map +1 -1
- package/dist/n2m-resolver.js +13 -13
- package/dist/n2m-resolver.js.map +1 -1
- package/dist/object-manager.d.ts +8 -0
- package/dist/object-manager.d.ts.map +1 -1
- package/dist/object-manager.js +23 -19
- package/dist/object-manager.js.map +1 -1
- package/dist/query-builder.d.ts +7 -7
- package/dist/query-builder.d.ts.map +1 -1
- package/dist/query-builder.js +28 -26
- package/dist/query-builder.js.map +1 -1
- package/dist/relation-resolver.d.ts +3 -3
- package/dist/relation-resolver.d.ts.map +1 -1
- package/dist/relation-resolver.js +5 -5
- package/dist/relation-resolver.js.map +1 -1
- package/package.json +12 -2
- package/src/hono/index.ts +237 -0
- package/src/hono/mount-read-only.ts +189 -0
- package/src/n2m-resolver.ts +19 -11
- package/src/object-manager.ts +34 -19
- package/src/query-builder.ts +49 -25
- package/src/relation-resolver.ts +6 -3
package/src/object-manager.ts
CHANGED
|
@@ -13,7 +13,10 @@ import {
|
|
|
13
13
|
resolvePkFields, compileFilter,
|
|
14
14
|
type Filter, type QueryOpts,
|
|
15
15
|
} from "./query-builder.js";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
buildNameMap, resolveTableName, DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
18
|
+
type ColumnNamingStrategy, type EntityNameMap,
|
|
19
|
+
} from "@metaobjectsdev/metadata";
|
|
17
20
|
import { coerceRowOnRead, coerceRowOnWrite } from "./type-coercer.js";
|
|
18
21
|
import { decodeRef, encodeRef } from "./ref-codec.js";
|
|
19
22
|
import { runValidators } from "./validator-runner.js";
|
|
@@ -37,6 +40,12 @@ import { VALID_ENTITY_NAME, DEFAULT_IF_MISSING } from "./constants.js";
|
|
|
37
40
|
export interface ObjectManagerOptions {
|
|
38
41
|
metadata: MetaData;
|
|
39
42
|
driver: PersistenceDriver;
|
|
43
|
+
/**
|
|
44
|
+
* Column-naming strategy for fields with no `@column` override. Defaults to
|
|
45
|
+
* `"snake_case"`. Must agree with the codegen / migration config that
|
|
46
|
+
* created the schema (a mismatch yields columns the runtime can't address).
|
|
47
|
+
*/
|
|
48
|
+
columnNamingStrategy?: ColumnNamingStrategy;
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
export interface ReadOpts extends QueryOpts {
|
|
@@ -54,17 +63,19 @@ export interface WriteOpts {
|
|
|
54
63
|
export class ObjectManager {
|
|
55
64
|
private readonly metadata: MetaData;
|
|
56
65
|
private readonly driver: PersistenceDriver;
|
|
66
|
+
private readonly columnNamingStrategy: ColumnNamingStrategy;
|
|
57
67
|
private readonly nameMapCache = new Map<string, EntityNameMap>();
|
|
58
68
|
|
|
59
69
|
constructor(opts: ObjectManagerOptions) {
|
|
60
70
|
this.metadata = opts.metadata;
|
|
61
71
|
this.driver = opts.driver;
|
|
72
|
+
this.columnNamingStrategy = opts.columnNamingStrategy ?? DEFAULT_COLUMN_NAMING_STRATEGY;
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
private nameMap(entity: MetaData): EntityNameMap {
|
|
65
76
|
let m = this.nameMapCache.get(entity.name);
|
|
66
77
|
if (!m) {
|
|
67
|
-
m = buildNameMap(entity);
|
|
78
|
+
m = buildNameMap(entity, this.columnNamingStrategy);
|
|
68
79
|
this.nameMapCache.set(entity.name, m);
|
|
69
80
|
}
|
|
70
81
|
return m;
|
|
@@ -79,7 +90,7 @@ export class ObjectManager {
|
|
|
79
90
|
async findFirst(entityName: string, filter: Filter, opts: ReadOpts = {}): Promise<Row | null> {
|
|
80
91
|
const entity = this.requireEntity(entityName);
|
|
81
92
|
const driver = opts.tx ?? this.driver;
|
|
82
|
-
const spec = buildSelectSpec(entity, filter, { ...opts, limit: 1 });
|
|
93
|
+
const spec = buildSelectSpec(entity, filter, { ...opts, limit: 1 }, undefined, this.columnNamingStrategy);
|
|
83
94
|
const row = await driver.selectOne(spec);
|
|
84
95
|
if (row === null) return null;
|
|
85
96
|
const jsRow = this.toJsRow(entity, row);
|
|
@@ -92,7 +103,7 @@ export class ObjectManager {
|
|
|
92
103
|
async findMany(entityName: string, filter?: Filter, opts: ReadOpts = {}): Promise<Row[]> {
|
|
93
104
|
const entity = this.requireEntity(entityName);
|
|
94
105
|
const driver = opts.tx ?? this.driver;
|
|
95
|
-
const spec = buildSelectSpec(entity, filter, opts);
|
|
106
|
+
const spec = buildSelectSpec(entity, filter, opts, undefined, this.columnNamingStrategy);
|
|
96
107
|
const rows = (await driver.selectMany(spec)).map((r) => this.toJsRow(entity, r));
|
|
97
108
|
if (opts.include && opts.include.length > 0) {
|
|
98
109
|
await this.attachIncludes(entity, rows, opts.include, driver);
|
|
@@ -103,7 +114,7 @@ export class ObjectManager {
|
|
|
103
114
|
async count(entityName: string, filter?: Filter, opts: Pick<ReadOpts, "tx"> = {}): Promise<number> {
|
|
104
115
|
const entity = this.requireEntity(entityName);
|
|
105
116
|
const driver = opts.tx ?? this.driver;
|
|
106
|
-
return driver.count(buildCountSpec(entity, filter));
|
|
117
|
+
return driver.count(buildCountSpec(entity, filter, this.columnNamingStrategy));
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
async load(refString: string): Promise<Row | null> {
|
|
@@ -144,7 +155,7 @@ export class ObjectManager {
|
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
const coerced = coerceRowOnWrite(entity, merged, driver.dialect);
|
|
147
|
-
const spec = buildInsertSpec(entity, coerced);
|
|
158
|
+
const spec = buildInsertSpec(entity, coerced, this.columnNamingStrategy);
|
|
148
159
|
const dbRow = await driver.insert(spec);
|
|
149
160
|
return this.toJsRow(entity, dbRow);
|
|
150
161
|
}
|
|
@@ -162,7 +173,7 @@ export class ObjectManager {
|
|
|
162
173
|
}
|
|
163
174
|
|
|
164
175
|
const coerced = coerceRowOnWrite(entity, restricted, driver.dialect);
|
|
165
|
-
const spec = buildUpdateSpec(entity, coerced, id);
|
|
176
|
+
const spec = buildUpdateSpec(entity, coerced, id, this.columnNamingStrategy);
|
|
166
177
|
const dbRow = await driver.update(spec);
|
|
167
178
|
if (dbRow === null) {
|
|
168
179
|
const mode = opts.ifMissing ?? DEFAULT_IF_MISSING;
|
|
@@ -175,7 +186,7 @@ export class ObjectManager {
|
|
|
175
186
|
async delete(entityName: string, id: unknown, opts: WriteOpts = {}): Promise<boolean> {
|
|
176
187
|
const entity = this.requireEntity(entityName);
|
|
177
188
|
const driver = opts.tx ?? this.driver;
|
|
178
|
-
const spec = buildDeleteSpec(entity, id);
|
|
189
|
+
const spec = buildDeleteSpec(entity, id, this.columnNamingStrategy);
|
|
179
190
|
const n = await driver.delete(spec);
|
|
180
191
|
if (n === 0) {
|
|
181
192
|
const mode = opts.ifMissing ?? DEFAULT_IF_MISSING;
|
|
@@ -228,7 +239,7 @@ export class ObjectManager {
|
|
|
228
239
|
const spec: UpdateManySpec = {
|
|
229
240
|
table: resolveTableName(entity),
|
|
230
241
|
values: this.toDbRow(entity, coerced),
|
|
231
|
-
where: requireNonEmptyFilter(entity, filter, "updateMany"),
|
|
242
|
+
where: requireNonEmptyFilter(entity, filter, "updateMany", this.columnNamingStrategy),
|
|
232
243
|
};
|
|
233
244
|
return driver.updateMany(spec);
|
|
234
245
|
}
|
|
@@ -238,14 +249,16 @@ export class ObjectManager {
|
|
|
238
249
|
const driver = opts.tx ?? this.driver;
|
|
239
250
|
const spec: DeleteManySpec = {
|
|
240
251
|
table: resolveTableName(entity),
|
|
241
|
-
where: requireNonEmptyFilter(entity, filter, "deleteMany"),
|
|
252
|
+
where: requireNonEmptyFilter(entity, filter, "deleteMany", this.columnNamingStrategy),
|
|
242
253
|
};
|
|
243
254
|
return driver.deleteMany(spec);
|
|
244
255
|
}
|
|
245
256
|
|
|
246
257
|
async transaction<T>(fn: (txOm: ObjectManager) => Promise<T>): Promise<T> {
|
|
247
258
|
return this.driver.transaction(async (txDriver) => {
|
|
248
|
-
const txOm = new ObjectManager({
|
|
259
|
+
const txOm = new ObjectManager({
|
|
260
|
+
metadata: this.metadata, driver: txDriver, columnNamingStrategy: this.columnNamingStrategy,
|
|
261
|
+
});
|
|
249
262
|
return fn(txOm);
|
|
250
263
|
});
|
|
251
264
|
}
|
|
@@ -258,7 +271,7 @@ export class ObjectManager {
|
|
|
258
271
|
const n2m = resolveN2mDescriptor(entity, relationName, this.metadata);
|
|
259
272
|
if (n2m !== null) {
|
|
260
273
|
const target = this.requireEntity(n2m.targetEntityName);
|
|
261
|
-
const { joinSpec, makeTargetSpec } = buildN2mLazySpecs(n2m, record, this.metadata);
|
|
274
|
+
const { joinSpec, makeTargetSpec } = buildN2mLazySpecs(n2m, record, this.metadata, this.columnNamingStrategy);
|
|
262
275
|
const joinRows = await driver.selectMany(joinSpec);
|
|
263
276
|
const targetSpec = makeTargetSpec(joinRows);
|
|
264
277
|
if (targetSpec === null) return [];
|
|
@@ -268,7 +281,7 @@ export class ObjectManager {
|
|
|
268
281
|
|
|
269
282
|
const desc = resolveRelationDescriptor(entity, relationName, this.metadata);
|
|
270
283
|
const target = this.requireEntity(desc.targetEntityName);
|
|
271
|
-
const spec = buildLazyRelateSpec(desc, record, this.metadata);
|
|
284
|
+
const spec = buildLazyRelateSpec(desc, record, this.metadata, this.columnNamingStrategy);
|
|
272
285
|
if (spec === null) return desc.cardinality === "one" ? null : [];
|
|
273
286
|
if (desc.cardinality === "one") {
|
|
274
287
|
const row = await driver.selectOne(spec);
|
|
@@ -311,15 +324,15 @@ export class ObjectManager {
|
|
|
311
324
|
const n2m = resolveN2mDescriptor(entity, inc, this.metadata);
|
|
312
325
|
if (n2m !== null) {
|
|
313
326
|
const target = this.requireEntity(n2m.targetEntityName);
|
|
314
|
-
const { joinSpec, makeTargetSpec } = buildN2mBatchSpecs(n2m, records, this.metadata);
|
|
327
|
+
const { joinSpec, makeTargetSpec } = buildN2mBatchSpecs(n2m, records, this.metadata, this.columnNamingStrategy);
|
|
315
328
|
const joinRows = await driver.selectMany(joinSpec);
|
|
316
329
|
const targetSpec = makeTargetSpec(joinRows);
|
|
317
330
|
const targetRows = targetSpec === null ? [] : (await driver.selectMany(targetSpec)).map((r) => this.toJsRow(target, r));
|
|
318
331
|
|
|
319
332
|
const sourcePk = resolvePkFields(entity)[0]!;
|
|
320
333
|
const joinEntity = this.requireEntity(n2m.joinEntityName);
|
|
321
|
-
const sourceJoinDbCol = resolveJoinColumnName(joinEntity, n2m.sourceJoinField);
|
|
322
|
-
const targetJoinDbCol = resolveJoinColumnName(joinEntity, n2m.targetJoinField);
|
|
334
|
+
const sourceJoinDbCol = resolveJoinColumnName(joinEntity, n2m.sourceJoinField, this.columnNamingStrategy);
|
|
335
|
+
const targetJoinDbCol = resolveJoinColumnName(joinEntity, n2m.targetJoinField, this.columnNamingStrategy);
|
|
323
336
|
const targetPk = resolvePkFields(target)[0]!;
|
|
324
337
|
const targetById = new Map(targetRows.map((r) => [r[targetPk], r]));
|
|
325
338
|
const grouped = new Map<unknown, Row[]>();
|
|
@@ -336,7 +349,7 @@ export class ObjectManager {
|
|
|
336
349
|
|
|
337
350
|
const desc = resolveRelationDescriptor(entity, inc, this.metadata);
|
|
338
351
|
const target = this.requireEntity(desc.targetEntityName);
|
|
339
|
-
const spec = buildIncludeBatchSpec(desc, records, this.metadata);
|
|
352
|
+
const spec = buildIncludeBatchSpec(desc, records, this.metadata, this.columnNamingStrategy);
|
|
340
353
|
const targetRows = spec === null ? [] : (await driver.selectMany(spec)).map((r) => this.toJsRow(target, r));
|
|
341
354
|
|
|
342
355
|
if (desc.cardinality === "one") {
|
|
@@ -412,8 +425,10 @@ export class ObjectManager {
|
|
|
412
425
|
|
|
413
426
|
// updateMany / deleteMany with an empty filter would silently affect every row.
|
|
414
427
|
// Force callers to be explicit (use $or-style or a tautology if they really mean "all").
|
|
415
|
-
function requireNonEmptyFilter(
|
|
416
|
-
|
|
428
|
+
function requireNonEmptyFilter(
|
|
429
|
+
entity: MetaData, filter: Filter, op: string, strategy: ColumnNamingStrategy,
|
|
430
|
+
): WhereClause {
|
|
431
|
+
const where = compileFilter(entity, filter, strategy);
|
|
417
432
|
if (where === null) {
|
|
418
433
|
throw new MetadataError(
|
|
419
434
|
`${op} on '${entity.name}' requires a non-empty filter — pass an explicit condition or use a per-row loop`,
|
package/src/query-builder.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { MetaData } from "@metaobjectsdev/metadata";
|
|
1
|
+
import type { ColumnNamingStrategy, MetaData } from "@metaobjectsdev/metadata";
|
|
2
2
|
import {
|
|
3
3
|
TYPE_FIELD, TYPE_IDENTITY,
|
|
4
4
|
IDENTITY_SUBTYPE_PRIMARY,
|
|
5
5
|
IDENTITY_ATTR_FIELDS,
|
|
6
|
+
DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
6
7
|
resolveTableName, resolveColumnName,
|
|
7
8
|
} from "@metaobjectsdev/metadata";
|
|
8
9
|
import { MetadataError } from "./errors.js";
|
|
@@ -69,11 +70,11 @@ function getField(entity: MetaData, fieldName: string): MetaData {
|
|
|
69
70
|
return f;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
function rowToColumns(entity: MetaData, data: Row): Row {
|
|
73
|
+
function rowToColumns(entity: MetaData, data: Row, strategy: ColumnNamingStrategy): Row {
|
|
73
74
|
const out: Row = {};
|
|
74
75
|
for (const [k, v] of Object.entries(data)) {
|
|
75
76
|
const field = getField(entity, k);
|
|
76
|
-
out[resolveColumnName(field)] = v;
|
|
77
|
+
out[resolveColumnName(field, strategy)] = v;
|
|
77
78
|
}
|
|
78
79
|
return out;
|
|
79
80
|
}
|
|
@@ -82,29 +83,37 @@ function rowToColumns(entity: MetaData, data: Row): Row {
|
|
|
82
83
|
* Returns null when `filter` has no clauses ({} or { $and: [] }) — meaning "match all"
|
|
83
84
|
* (callers should treat null the same as omitting the where clause entirely).
|
|
84
85
|
*/
|
|
85
|
-
export function compileFilter(
|
|
86
|
+
export function compileFilter(
|
|
87
|
+
entity: MetaData,
|
|
88
|
+
filter: Filter,
|
|
89
|
+
strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
90
|
+
): WhereClause | null {
|
|
86
91
|
if ("$and" in filter && Array.isArray(filter.$and)) {
|
|
87
92
|
// TS can't narrow $and away from the Record branch, so the runtime check above
|
|
88
93
|
// proves it's a Filter[] but the element type still needs a bridge cast.
|
|
89
94
|
const andFilters = filter.$and as Filter[];
|
|
90
|
-
const clauses = andFilters
|
|
95
|
+
const clauses = andFilters
|
|
96
|
+
.map((f) => compileFilter(entity, f, strategy))
|
|
97
|
+
.filter((c): c is WhereClause => c !== null);
|
|
91
98
|
if (clauses.length === 0) return null;
|
|
92
99
|
return clauses.length === 1 ? clauses[0]! : { kind: "and", clauses };
|
|
93
100
|
}
|
|
94
101
|
const entries = Object.entries(filter);
|
|
95
102
|
if (entries.length === 0) return null;
|
|
96
103
|
if (entries.length === 1) {
|
|
97
|
-
return compileEntry(entity, entries[0]![0], entries[0]![1] as FilterValue);
|
|
104
|
+
return compileEntry(entity, entries[0]![0], entries[0]![1] as FilterValue, strategy);
|
|
98
105
|
}
|
|
99
106
|
return {
|
|
100
107
|
kind: "and",
|
|
101
|
-
clauses: entries.map(([k, v]) => compileEntry(entity, k, v as FilterValue)),
|
|
108
|
+
clauses: entries.map(([k, v]) => compileEntry(entity, k, v as FilterValue, strategy)),
|
|
102
109
|
};
|
|
103
110
|
}
|
|
104
111
|
|
|
105
|
-
function compileEntry(
|
|
112
|
+
function compileEntry(
|
|
113
|
+
entity: MetaData, fieldName: string, value: FilterValue, strategy: ColumnNamingStrategy,
|
|
114
|
+
): WhereClause {
|
|
106
115
|
const field = getField(entity, fieldName);
|
|
107
|
-
const column = resolveColumnName(field);
|
|
116
|
+
const column = resolveColumnName(field, strategy);
|
|
108
117
|
|
|
109
118
|
if (value === null) return { kind: "isNull", column, not: false };
|
|
110
119
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
@@ -137,12 +146,14 @@ function compileEntry(entity: MetaData, fieldName: string, value: FilterValue):
|
|
|
137
146
|
throw new MetadataError(`No recognized operator on filter for field '${fieldName}'`);
|
|
138
147
|
}
|
|
139
148
|
|
|
140
|
-
function normalizeOrderBy(
|
|
149
|
+
function normalizeOrderBy(
|
|
150
|
+
input: QueryOpts["orderBy"], entity: MetaData, strategy: ColumnNamingStrategy,
|
|
151
|
+
): OrderBy[] | undefined {
|
|
141
152
|
if (input === undefined) return undefined;
|
|
142
153
|
const arr = Array.isArray(input[0]) ? (input as [string, "asc" | "desc"][]) : [input as [string, "asc" | "desc"]];
|
|
143
154
|
return arr.map(([fieldName, dir]) => {
|
|
144
155
|
const field = getField(entity, fieldName);
|
|
145
|
-
return { column: resolveColumnName(field), direction: dir };
|
|
156
|
+
return { column: resolveColumnName(field, strategy), direction: dir };
|
|
146
157
|
});
|
|
147
158
|
}
|
|
148
159
|
|
|
@@ -152,20 +163,21 @@ export function buildSelectSpec(
|
|
|
152
163
|
filter: Filter | undefined,
|
|
153
164
|
opts: QueryOpts,
|
|
154
165
|
projectedFields?: string[],
|
|
166
|
+
strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
155
167
|
): SelectSpec {
|
|
156
168
|
const allFields = projectedFields ?? listFieldNames(entity);
|
|
157
169
|
const pkFields = resolvePkFields(entity);
|
|
158
170
|
const fieldSet = new Set<string>(allFields);
|
|
159
171
|
for (const pk of pkFields) fieldSet.add(pk);
|
|
160
172
|
|
|
161
|
-
const columns = [...fieldSet].map((f) => resolveColumnName(getField(entity, f)));
|
|
162
|
-
const orderBy = normalizeOrderBy(opts.orderBy, entity);
|
|
173
|
+
const columns = [...fieldSet].map((f) => resolveColumnName(getField(entity, f), strategy));
|
|
174
|
+
const orderBy = normalizeOrderBy(opts.orderBy, entity, strategy);
|
|
163
175
|
|
|
164
176
|
const spec: SelectSpec = {
|
|
165
177
|
table: resolveTableName(entity),
|
|
166
178
|
columns,
|
|
167
179
|
};
|
|
168
|
-
const where = filter !== undefined ? compileFilter(entity, filter) : null;
|
|
180
|
+
const where = filter !== undefined ? compileFilter(entity, filter, strategy) : null;
|
|
169
181
|
if (where !== null) spec.where = where;
|
|
170
182
|
if (orderBy !== undefined) spec.orderBy = orderBy;
|
|
171
183
|
if (opts.limit !== undefined) spec.limit = opts.limit;
|
|
@@ -173,25 +185,34 @@ export function buildSelectSpec(
|
|
|
173
185
|
return spec;
|
|
174
186
|
}
|
|
175
187
|
|
|
176
|
-
export function buildCountSpec(
|
|
188
|
+
export function buildCountSpec(
|
|
189
|
+
entity: MetaData, filter: Filter | undefined,
|
|
190
|
+
strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
191
|
+
): CountSpec {
|
|
177
192
|
const spec: CountSpec = { table: resolveTableName(entity) };
|
|
178
|
-
const where = filter !== undefined ? compileFilter(entity, filter) : null;
|
|
193
|
+
const where = filter !== undefined ? compileFilter(entity, filter, strategy) : null;
|
|
179
194
|
if (where !== null) spec.where = where;
|
|
180
195
|
return spec;
|
|
181
196
|
}
|
|
182
197
|
|
|
183
|
-
export function buildInsertSpec(
|
|
184
|
-
|
|
198
|
+
export function buildInsertSpec(
|
|
199
|
+
entity: MetaData, data: Row,
|
|
200
|
+
strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
201
|
+
): InsertSpec {
|
|
202
|
+
const values = rowToColumns(entity, data, strategy);
|
|
185
203
|
const allFields = listFieldNames(entity);
|
|
186
204
|
return {
|
|
187
205
|
table: resolveTableName(entity),
|
|
188
206
|
values,
|
|
189
|
-
returning: allFields.map((f) => resolveColumnName(getField(entity, f))),
|
|
207
|
+
returning: allFields.map((f) => resolveColumnName(getField(entity, f), strategy)),
|
|
190
208
|
};
|
|
191
209
|
}
|
|
192
210
|
|
|
193
|
-
export function buildUpdateSpec(
|
|
194
|
-
|
|
211
|
+
export function buildUpdateSpec(
|
|
212
|
+
entity: MetaData, data: Row, id: unknown,
|
|
213
|
+
strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
214
|
+
): UpdateSpec {
|
|
215
|
+
const values = rowToColumns(entity, data, strategy);
|
|
195
216
|
const pkFields = resolvePkFields(entity);
|
|
196
217
|
if (pkFields.length !== 1) {
|
|
197
218
|
throw new MetadataError(
|
|
@@ -199,16 +220,19 @@ export function buildUpdateSpec(entity: MetaData, data: Row, id: unknown): Updat
|
|
|
199
220
|
{ entity: entity.name },
|
|
200
221
|
);
|
|
201
222
|
}
|
|
202
|
-
const pkColumn = resolveColumnName(getField(entity, pkFields[0]!));
|
|
223
|
+
const pkColumn = resolveColumnName(getField(entity, pkFields[0]!), strategy);
|
|
203
224
|
return {
|
|
204
225
|
table: resolveTableName(entity),
|
|
205
226
|
values,
|
|
206
227
|
where: { kind: "eq", column: pkColumn, value: id as PrimitiveValue },
|
|
207
|
-
returning: listFieldNames(entity).map((f) => resolveColumnName(getField(entity, f))),
|
|
228
|
+
returning: listFieldNames(entity).map((f) => resolveColumnName(getField(entity, f), strategy)),
|
|
208
229
|
};
|
|
209
230
|
}
|
|
210
231
|
|
|
211
|
-
export function buildDeleteSpec(
|
|
232
|
+
export function buildDeleteSpec(
|
|
233
|
+
entity: MetaData, id: unknown,
|
|
234
|
+
strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
235
|
+
): DeleteSpec {
|
|
212
236
|
const pkFields = resolvePkFields(entity);
|
|
213
237
|
if (pkFields.length !== 1) {
|
|
214
238
|
throw new MetadataError(
|
|
@@ -216,7 +240,7 @@ export function buildDeleteSpec(entity: MetaData, id: unknown): DeleteSpec {
|
|
|
216
240
|
{ entity: entity.name },
|
|
217
241
|
);
|
|
218
242
|
}
|
|
219
|
-
const pkColumn = resolveColumnName(getField(entity, pkFields[0]!));
|
|
243
|
+
const pkColumn = resolveColumnName(getField(entity, pkFields[0]!), strategy);
|
|
220
244
|
return {
|
|
221
245
|
table: resolveTableName(entity),
|
|
222
246
|
where: { kind: "eq", column: pkColumn, value: id as PrimitiveValue },
|
package/src/relation-resolver.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MetaData } from "@metaobjectsdev/metadata";
|
|
1
|
+
import type { ColumnNamingStrategy, MetaData } from "@metaobjectsdev/metadata";
|
|
2
2
|
import {
|
|
3
3
|
TYPE_OBJECT, TYPE_RELATIONSHIP, TYPE_IDENTITY,
|
|
4
4
|
IDENTITY_SUBTYPE_REFERENCE,
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
IDENTITY_REFERENCE_ATTR_REFERENCES,
|
|
7
7
|
RELATIONSHIP_ATTR_CARDINALITY, RELATIONSHIP_ATTR_OBJECT_REF,
|
|
8
8
|
CARDINALITY_ONE, CARDINALITY_MANY,
|
|
9
|
+
DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
9
10
|
} from "@metaobjectsdev/metadata";
|
|
10
11
|
import { MetadataError } from "./errors.js";
|
|
11
12
|
import {
|
|
@@ -140,11 +141,12 @@ export function buildLazyRelateSpec(
|
|
|
140
141
|
desc: RelationDescriptor,
|
|
141
142
|
sourceRecord: Row,
|
|
142
143
|
root: MetaData,
|
|
144
|
+
strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
143
145
|
): SelectSpec | null {
|
|
144
146
|
const lookup = sourceRecord[desc.sourceField];
|
|
145
147
|
if (lookup === null || lookup === undefined) return null;
|
|
146
148
|
const target = mustGetEntity(root, desc.targetEntityName);
|
|
147
|
-
return buildSelectSpec(target, { [desc.targetField]: lookup as PrimitiveValue }, {});
|
|
149
|
+
return buildSelectSpec(target, { [desc.targetField]: lookup as PrimitiveValue }, {}, undefined, strategy);
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
/** Builds one batched IN(...) lookup. Returns null when there are no non-null source values. */
|
|
@@ -152,6 +154,7 @@ export function buildIncludeBatchSpec(
|
|
|
152
154
|
desc: RelationDescriptor,
|
|
153
155
|
sourceRecords: Row[],
|
|
154
156
|
root: MetaData,
|
|
157
|
+
strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
155
158
|
): SelectSpec | null {
|
|
156
159
|
const seen = new Set<PrimitiveValue>();
|
|
157
160
|
for (const rec of sourceRecords) {
|
|
@@ -161,7 +164,7 @@ export function buildIncludeBatchSpec(
|
|
|
161
164
|
}
|
|
162
165
|
if (seen.size === 0) return null;
|
|
163
166
|
const target = mustGetEntity(root, desc.targetEntityName);
|
|
164
|
-
return buildSelectSpec(target, { [desc.targetField]: [...seen] as (string | number)[] }, {});
|
|
167
|
+
return buildSelectSpec(target, { [desc.targetField]: [...seen] as (string | number)[] }, {}, undefined, strategy);
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
function mustGetEntity(root: MetaData, name: string): MetaData {
|