Skip to content

Erweiterbare Registrar-Methoden

Core Features Konzept

Core Features werden vom Framework mitgeliefert, folgen aber der gleichen defineFeature() API wie App Features. Der einzige Unterschied: sie leben in packages/framework/src/features/ und andere Features koennen requires: ["jobs"] darauf setzen.

packages/framework/
src/
features/ ← Core Features (vom Framework)
config/ ← Tenant/User Config, UI Sektionen
files/ ← File Storage, Upload/Download
jobs/ ← Job Queue, Worker, UI, Logs
engine/
db/
...
features/ ← App Features (vom User)
admin-users/
user-profile/

Features deklarieren Abhaengigkeiten:

export default defineFeature("adminUsers", (r) => {
r.requires("jobs"); // Framework validiert beim Boot
// ...
});

Das Problem

// Jemand will Custom Fields — muesste "customFields: true" in den EntityDefinition-Typ einfuegen
// Jemand will Tags — muesste "taggable: true" einfuegen
// Jemand will Audit — muesste "audited: true" einfuegen
// → Framework-Core wird zum Bottleneck, Package-Nutzer koennen nicht erweitern

Loesung: r.extendsRegistrar()

Features erweitern Schema, Pipeline und UI ueber 5 Ebenen:

r.extendsRegistrar("customFields", {
// 1. Registrierung: wird aufgerufen wenn ein Feature r.customFields("property") nutzt
onRegister: (entityName: string, options?: ExtensionOptions) => { ... },
// 2. Schema-Erweiterung: fuegt Spalten zur Entity hinzu
extendSchema: (entityName: string) => ({
customFields: { type: "jsonb", default: {} },
}),
// 3. Pipeline-Hooks: CRUD-Verhalten erweitern
hooks: {
preSave: (changes, ctx) => { /* Custom Fields validieren */ },
postRead: (data, ctx) => { /* Custom Fields zum Response hinzufuegen */ },
},
// 4. Search-Erweiterung: zusaetzliche Felder indexieren
extendSearch: (entityName: string, ctx) => {
/* Searchable Custom Fields zum Meilisearch Index hinzufuegen */
},
// 5. UI-Erweiterung: Formular und Liste erweitern
uiExtension: {
editSection: "customFieldsSection",
listColumns: "customFieldColumns",
filters: "customFieldFilters",
},
});

Die 5 Erweiterungs-Ebenen

EbeneWasBeispiel Custom FieldsBeispiel Tags
onRegisterRegistrierungEntity in FieldDefinitions trackenEntity in Pivot-Table tracken
extendSchemaDB-Spalten hinzufuegenJSONB customFields Spalte- (eigene Pivot-Table)
hooksCRUD-Pipeline erweiternValidierung + Read/WriteResponse + Filter erweitern
extendSearchSearch-Index erweiternDynamische Felder indexierenTags-Array indexieren
uiExtensionUI-Komponenten einklinkenFormular-Sektion + SpaltenChips + Picker + Filter

Nicht jede Erweiterung braucht alle 5 Ebenen.

Nutzung

defineFeature("fleet", (r) => {
r.requires("customFields", "tags", "config");
r.entity("property", {
fields: {
name: { type: "text" },
photos: { type: "images", maxSize: "5mb", accept: ["jpg", "png"], maxCount: 50 },
},
});
r.customFields("property"); // Vom Custom Fields Feature
r.tags("property"); // Vom Tags-Feature
r.config({ keys: { ... } }); // Vom Config-Feature
});

Eigene Erweiterungen

defineFeature("comments", (r) => {
r.entity("comment", { fields: { ... } });
r.extendsRegistrar("commentable", {
onRegister: (entityName) => { /* Track entity */ },
hooks: {
postRead: (data, ctx) => { /* Comments Count mitlesen */ },
},
uiExtension: {
editSection: "commentsSection",
},
});
});
defineFeature("tickets", (r) => {
r.requires("comments");
r.entity("ticket", { fields: { ... } });
r.commentable("ticket");
});

Was im Framework-Core bleibt (nicht erweiterbar)

  • r.entity() — Feld-Typen inkl. file, image, files, images, encrypted
  • r.writeHandler(), r.queryHandler(), r.crud()
  • r.relation(), r.hook(), r.translations()
  • r.requires(), r.screen(), r.nav()
  • r.extendsRegistrar() — das Meta-Feature das Erweiterungen ermoeglicht

Was ueber Registrar-Erweiterungen kommt

  • r.config() — vom Config-Feature
  • r.job(), r.scheduledJob() — vom Jobs-Feature
  • r.customFields() — vom Custom Fields Feature
  • r.tags() — vom Tags-Feature
  • r.audited() — spaeter vom Audit-Feature
  • r.importable() — spaeter vom Import-Feature