Merge remote-tracking branch 'origin/release/0.20' into feat/postgres-fc

# Conflicts:
#	app/src/core/utils/runtime.ts
This commit is contained in:
dswbx
2025-12-02 10:20:58 +01:00
133 changed files with 3896 additions and 244 deletions

View File

@@ -96,6 +96,9 @@ export class DataController extends Controller {
// read entity schema
hono.get(
"/schema.json",
permission(SystemPermissions.schemaRead, {
context: (_c) => ({ module: "data" }),
}),
permission(DataPermissions.entityRead, {
context: (c) => ({ entity: c.req.param("entity") }),
}),
@@ -124,6 +127,9 @@ export class DataController extends Controller {
// read schema
hono.get(
"/schemas/:entity/:context?",
permission(SystemPermissions.schemaRead, {
context: (_c) => ({ module: "data" }),
}),
permission(DataPermissions.entityRead, {
context: (c) => ({ entity: c.req.param("entity") }),
}),
@@ -161,7 +167,7 @@ export class DataController extends Controller {
hono.get(
"/types",
permission(SystemPermissions.schemaRead, {
context: (c) => ({ module: "data" }),
context: (_c) => ({ module: "data" }),
}),
describeRoute({
summary: "Retrieve data typescript definitions",
@@ -182,6 +188,9 @@ export class DataController extends Controller {
*/
hono.get(
"/info/:entity",
permission(SystemPermissions.schemaRead, {
context: (_c) => ({ module: "data" }),
}),
permission(DataPermissions.entityRead, {
context: (c) => ({ entity: c.req.param("entity") }),
}),

View File

@@ -103,6 +103,7 @@ export class Repository<TBD extends object = DefaultDB, TB extends keyof TBD = a
validated.with = options.with;
}
// add explicit joins. Implicit joins are added in `where` builder
if (options.join && options.join.length > 0) {
for (const entry of options.join) {
const related = this.em.relationOf(entity.name, entry);
@@ -127,12 +128,28 @@ export class Repository<TBD extends object = DefaultDB, TB extends keyof TBD = a
const invalid = WhereBuilder.getPropertyNames(options.where).filter((field) => {
if (field.includes(".")) {
const [alias, prop] = field.split(".") as [string, string];
if (!aliases.includes(alias)) {
// check aliases first (added joins)
if (aliases.includes(alias)) {
this.checkIndex(alias, prop, "where");
return !this.em.entity(alias).getField(prop);
}
// check if alias (entity) exists
if (!this.em.hasEntity(alias)) {
return true;
}
// check related fields for auto join
const related = this.em.relationOf(entity.name, alias);
if (related) {
const other = related.other(entity);
if (other.entity.getField(prop)) {
// if related field is found, add join to validated options
validated.join?.push(alias);
this.checkIndex(alias, prop, "where");
return false;
}
}
this.checkIndex(alias, prop, "where");
return !this.em.entity(alias).getField(prop);
return true;
}
this.checkIndex(entity.name, field, "where");

View File

@@ -4,6 +4,7 @@ import type { KyselyJsonFrom } from "data/relations/EntityRelation";
import type { RepoQuery } from "data/server/query";
import { InvalidSearchParamsException } from "data/errors";
import type { Entity, EntityManager, RepositoryQB } from "data/entities";
import { $console } from "bknd/utils";
export class WithBuilder {
static addClause(
@@ -13,7 +14,7 @@ export class WithBuilder {
withs: RepoQuery["with"],
) {
if (!withs || !isObject(withs)) {
console.warn(`'withs' undefined or invalid, given: ${JSON.stringify(withs)}`);
$console.warn(`'withs' undefined or invalid, given: ${JSON.stringify(withs)}`);
return qb;
}
@@ -37,9 +38,7 @@ export class WithBuilder {
let subQuery = relation.buildWith(entity, ref)(eb);
if (query) {
subQuery = em.repo(other.entity).addOptionsToQueryBuilder(subQuery, query as any, {
ignore: ["with", "join", cardinality === 1 ? "limit" : undefined].filter(
Boolean,
) as any,
ignore: ["with", cardinality === 1 ? "limit" : undefined].filter(Boolean) as any,
});
}
@@ -57,7 +56,7 @@ export class WithBuilder {
static validateWiths(em: EntityManager<any>, entity: string, withs: RepoQuery["with"]) {
let depth = 0;
if (!withs || !isObject(withs)) {
withs && console.warn(`'withs' invalid, given: ${JSON.stringify(withs)}`);
withs && $console.warn(`'withs' invalid, given: ${JSON.stringify(withs)}`);
return depth;
}

View File

@@ -26,7 +26,12 @@ export class JsonSchemaField<
constructor(name: string, config: Partial<JsonSchemaFieldConfig>) {
super(name, config);
this.validator = new Validator({ ...this.getJsonSchema() });
// make sure to hand over clean json
const schema = this.getJsonSchema();
this.validator = new Validator(
typeof schema === "object" ? JSON.parse(JSON.stringify(schema)) : {},
);
}
protected getSchema() {

View File

@@ -52,7 +52,7 @@ export class NumberField<Required extends true | false = false> extends Field<
switch (context) {
case "submit":
return Number.parseInt(value);
return Number.parseInt(value, 10);
}
return value;

View File

@@ -28,7 +28,7 @@ export function getChangeSet(
const value = _value === "" ? null : _value;
// normalize to null if undefined
const newValue = field.getValue(value, "submit") || null;
const newValue = field.getValue(value, "submit") ?? null;
// @todo: add typing for "action"
if (action === "create" || newValue !== data[key]) {
acc[key] = newValue;

View File

@@ -289,7 +289,7 @@ class EntityManagerPrototype<Entities extends Record<string, Entity>> extends En
super(Object.values(__entities), new DummyConnection(), relations, indices);
}
withConnection(connection: Connection): EntityManager<Schema<Entities>> {
withConnection(connection: Connection): EntityManager<Schemas<Entities>> {
return new EntityManager(this.entities, connection, this.relations.all, this.indices);
}
}