Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions server/typescript/packages/codegen-ts/src/column-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ function pgColumnTypeOverride(
field: MetaField,
timestampMode: "date" | "string" = "string",
): { fnName: string; fnOptions?: Record<string, unknown> } | undefined {
const dbColumnType = field.ownAttr(FIELD_ATTR_DB_COLUMN_TYPE);
const dbColumnType = field.attr(FIELD_ATTR_DB_COLUMN_TYPE);
if (typeof dbColumnType !== "string") return undefined;
switch (dbColumnType) {
case DB_COLUMN_TYPE_UUID:
Expand All @@ -191,7 +191,7 @@ function pgColumnTypeOverride(
/** Resolve max length from validator.length child or @maxLength attr.
* Uses field.validators() (effective) so inherited validators are seen. */
function getMaxLength(field: MetaField): number | undefined {
const lenAttr = field.ownAttr(FIELD_ATTR_MAX_LENGTH);
const lenAttr = field.attr(FIELD_ATTR_MAX_LENGTH);
if (typeof lenAttr === "number") return lenAttr;
for (const child of field.validators()) {
if (child.subType === VALIDATOR_SUBTYPE_LENGTH) {
Expand All @@ -205,15 +205,15 @@ function getMaxLength(field: MetaField): number | undefined {
/** Check for validator.required child OR @required attr.
* Uses field.validators() (effective) so inherited validators are seen. */
function isRequired(field: MetaField): boolean {
if (field.ownAttr(FIELD_ATTR_REQUIRED) === true) return true;
if (field.attr(FIELD_ATTR_REQUIRED) === true) return true;
return field.validators().some((child) => child.subType === VALIDATOR_SUBTYPE_REQUIRED);
}

/** The bare (package-stripped) @objectRef name on a field.object, or undefined
* when unset. Used as the `.$type<VO>()` target + its sibling-module import.
* A fully-qualified ref (acme::ai::SourceLens) strips to the short name. */
function objectRefBaseName(field: MetaField): string | undefined {
const ref = field.ownAttr(FIELD_ATTR_OBJECT_REF);
const ref = field.attr(FIELD_ATTR_OBJECT_REF);
if (typeof ref === "string" && ref.length > 0) return stripPackage(ref);
return undefined;
}
Expand Down Expand Up @@ -339,8 +339,8 @@ export function mapColumnType(
// declared @precision/@scale (mirroring migrate-ts/expected-schema so
// codegen and the DDL agree), falling back to a sane default.
fnName = "numeric";
const precision = field.ownAttr(FIELD_ATTR_PRECISION);
const scale = field.ownAttr(FIELD_ATTR_SCALE);
const precision = field.attr(FIELD_ATTR_PRECISION);
const scale = field.attr(FIELD_ATTR_SCALE);
if (typeof precision === "number" && typeof scale === "number") {
fnOptions = { precision, scale };
} else if (typeof precision === "number") {
Expand Down Expand Up @@ -414,12 +414,12 @@ export function mapColumnType(
modifiers.push(".notNull()");
}

if (field.ownAttr(FIELD_ATTR_UNIQUE) === true) {
if (field.attr(FIELD_ATTR_UNIQUE) === true) {
modifiers.push(".unique()");
}

let defaultExpr: DefaultExpr | undefined;
const defaultAttr = field.ownAttr(FIELD_ATTR_DEFAULT);
const defaultAttr = field.attr(FIELD_ATTR_DEFAULT);
if (defaultAttr !== undefined) {
// SQL-expression detection runs on the raw string value — a string like
// "CURRENT_TIMESTAMP" or "now" must be emitted as sql`...`, not a literal.
Expand Down
2 changes: 1 addition & 1 deletion server/typescript/packages/codegen-ts/src/enum-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function sharedEnumForField(field: MetaField): SharedEnum | undefined {
return {
name: toPascalCase(decl.name),
values,
provided: decl.ownAttr(FIELD_ATTR_PROVIDED) === true,
provided: decl.attr(FIELD_ATTR_PROVIDED) === true,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export interface BuildDocDataOpts {
* the documented model-field optionality. Exported so the field-shape builder
* reuses the EXACT same rule rather than re-deriving it. */
export function isFieldRequired(field: MetaField): boolean {
if (field.ownAttr(FIELD_ATTR_REQUIRED) === true) return true;
if (field.attr(FIELD_ATTR_REQUIRED) === true) return true;
return field.validators().some((v) => v.subType === VALIDATOR_SUBTYPE_REQUIRED);
}

Expand All @@ -104,7 +104,7 @@ interface ValidatorParts {
}

function collectValidatorParts(field: MetaField): ValidatorParts {
const maxLenAttr = field.ownAttr(FIELD_ATTR_MAX_LENGTH);
const maxLenAttr = field.attr(FIELD_ATTR_MAX_LENGTH);
const regexParts: string[] = [];
const lengthParts: string[] = [];
const numericParts: string[] = [];
Expand Down Expand Up @@ -143,7 +143,7 @@ function collectValidatorParts(field: MetaField): ValidatorParts {
export function neutralTypeStr(field: MetaField): string {
let base: string;
if (field.subType === FIELD_SUBTYPE_OBJECT) {
const ref = field.ownAttr(FIELD_ATTR_OBJECT_REF);
const ref = field.attr(FIELD_ATTR_OBJECT_REF);
base = typeof ref === "string" && ref.length > 0 ? stripPackage(ref) : "object";
} else {
base = field.subType;
Expand All @@ -165,7 +165,7 @@ function neutralTypeCell(field: MetaField): string {
* uses. Deliberately does NOT derive ANSI/ORM SQL so it can't drift vs the
* migrate engine or re-introduce language-specific DDL. Wrapped in backticks. */
function storageTypeCell(field: MetaField): string {
const dbColumnType = field.ownAttr(FIELD_ATTR_DB_COLUMN_TYPE);
const dbColumnType = field.attr(FIELD_ATTR_DB_COLUMN_TYPE);
if (typeof dbColumnType === "string" && dbColumnType.length > 0) {
return `\`${dbColumnType.toUpperCase()}\``;
}
Expand All @@ -190,7 +190,7 @@ function buildConstraintRow(
const rules: string[] = [];

if (isPk) rules.push("primary key");
if (field.ownAttr(FIELD_ATTR_UNIQUE) === true) rules.push("unique");
if (field.attr(FIELD_ATTR_UNIQUE) === true) rules.push("unique");

if (field.subType === FIELD_SUBTYPE_ENUM && !field.isArray) {
const values = enumValues(field);
Expand All @@ -212,7 +212,7 @@ function buildConstraintRow(
rules.push(`references \`${fk.targetEntity}.${fk.targetField}\``);
}

const def = field.ownAttr(FIELD_ATTR_DEFAULT);
const def = field.attr(FIELD_ATTR_DEFAULT);
if (def !== undefined) rules.push(`default: \`${String(def)}\``);

const sup = field.resolveSuper();
Expand Down Expand Up @@ -271,7 +271,7 @@ function buildFieldRow(
// Otherwise empty. Keeps the column noise-free for the 90% case where
// field name and column name agree.
const columnName = field.column;
const dbColumnType = field.ownAttr(FIELD_ATTR_DB_COLUMN_TYPE);
const dbColumnType = field.attr(FIELD_ATTR_DB_COLUMN_TYPE);
const columnDiffers = typeof columnName === "string" && columnName !== field.name;
const hasPhysicalOverride = typeof dbColumnType === "string" && dbColumnType.length > 0;
let storageCell = "";
Expand All @@ -287,7 +287,7 @@ function buildFieldRow(
// column, plus the maxLength/length/numeric limits that used to live in
// the separate Limits cell (collapsed in to keep the table to 5 columns).
const rules: string[] = [];
if (field.ownAttr(FIELD_ATTR_UNIQUE) === true) rules.push("unique");
if (field.attr(FIELD_ATTR_UNIQUE) === true) rules.push("unique");

if (field.subType === FIELD_SUBTYPE_ENUM && !field.isArray) {
const values = enumValues(field);
Expand All @@ -303,7 +303,7 @@ function buildFieldRow(
rules.push(...lengthParts, ...numericParts);

// The FK reference is already encoded in typeCell — don't repeat it in rules.
const def = field.ownAttr(FIELD_ATTR_DEFAULT);
const def = field.attr(FIELD_ATTR_DEFAULT);
if (def !== undefined) rules.push(`default: \`${String(def)}\``);

const sup = field.resolveSuper();
Expand Down Expand Up @@ -340,10 +340,10 @@ function buildFieldDetail(
const hasSummary = typeof summary === "string" && summary.length > 0;
const sup = field.resolveSuper();
const fk = fkMap.get(field.name);
const def = field.ownAttr(FIELD_ATTR_DEFAULT);
const def = field.attr(FIELD_ATTR_DEFAULT);
const columnName = field.column;
const dbColumnType = field.ownAttr(FIELD_ATTR_DB_COLUMN_TYPE);
const isUnique = field.ownAttr(FIELD_ATTR_UNIQUE) === true;
const dbColumnType = field.attr(FIELD_ATTR_DB_COLUMN_TYPE);
const isUnique = field.attr(FIELD_ATTR_UNIQUE) === true;
const isEnum = field.subType === FIELD_SUBTYPE_ENUM && !field.isArray;
const enumVals = isEnum ? enumValues(field) : undefined;
const validators = field.validators();
Expand All @@ -352,7 +352,7 @@ function buildFieldDetail(
|| v.subType === VALIDATOR_SUBTYPE_REGEX
|| v.subType === VALIDATOR_SUBTYPE_NUMERIC,
);
const maxLenAttr = field.ownAttr(FIELD_ATTR_MAX_LENGTH);
const maxLenAttr = field.attr(FIELD_ATTR_MAX_LENGTH);

// "Interesting enough to render a detail block" predicate. Plain typed
// fields with no authored annotations get skipped — the at-a-glance Fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function buildEnrichedPayloadTree(
required: isFieldRequired(f),
};
if (f.subType === FIELD_SUBTYPE_OBJECT) {
const ref = f.ownAttr(FIELD_ATTR_OBJECT_REF);
const ref = f.attr(FIELD_ATTR_OBJECT_REF);
if (typeof ref === "string" && ref.length > 0) {
// Owner switches to the nested VO for its fields (the recursion below
// re-derives `owner` from the resolved VO's own name).
Expand Down
4 changes: 2 additions & 2 deletions server/typescript/packages/codegen-ts/src/payload-codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function fieldTsType(
ownerName: string,
): { type: string; refVo?: string; enumAlias?: { name: string; decl: string } } {
if (field.subType === FIELD_SUBTYPE_OBJECT) {
const ref = field.ownAttr(FIELD_ATTR_OBJECT_REF);
const ref = field.attr(FIELD_ATTR_OBJECT_REF);
const refName = typeof ref === "string" ? ref : "unknown";
// isArray is a structural property on MetaData, not an attr.
const isArray = field.isArray;
Expand Down Expand Up @@ -96,7 +96,7 @@ function fieldTsType(

/** True iff the field's @required is explicitly set to true. */
function isFieldRequired(field: MetaData): boolean {
return field.ownAttr(FIELD_ATTR_REQUIRED) === true;
return field.attr(FIELD_ATTR_REQUIRED) === true;
}

function emitInterface(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ function sourceColumnNameFor(
entityField: MetaData,
ctx: ExtractContext,
): string {
const col = entityField.ownAttr(FIELD_ATTR_COLUMN);
const col = entityField.attr(FIELD_ATTR_COLUMN);
if (typeof col === "string" && col !== "") return col;
return columnNameFromField(entityField.name, ctx.columnNamingStrategy);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// plus the relations() block auto-emitted at the end.

import { code, imp, joinCode, type Code } from "ts-poet";
import { MetaObject, MetaField } from "@metaobjectsdev/metadata";
import { MetaObject, MetaField, stripPackage } from "@metaobjectsdev/metadata";
import {
IDENTITY_SUBTYPE_SECONDARY, FIELD_SUBTYPE_LONG,
IDENTITY_ATTR_FIELDS, IDENTITY_ATTR_GENERATION, IDENTITY_ATTR_UNIQUE,
Expand Down Expand Up @@ -184,7 +184,11 @@ function buildFkMapForEntity(obj: MetaObject, ctx: RenderContext): Map<string, F
const fkField = fkFieldNames[0]!;
const targetName = ref.targetEntity;
if (!targetName) continue;
const targetObj = ctx.loadedRoot.findObject(targetName);
// @references may be authored bare OR package-qualified, and the loader can
// resolve it to an FQN (e.g. the YAML front-end qualifies it). Strip the
// package so the lookup matches the object's bare name — mirrors the
// relation-resolver, which already does this.
const targetObj = ctx.loadedRoot.findObject(stripPackage(targetName));
if (!targetObj) continue;
const targetPkField = ref.resolvedTargetPkField(ctx.loadedRoot) ?? "id";
result.set(fkField, {
Expand Down Expand Up @@ -345,7 +349,7 @@ function renderColumn(
// @autoSet fields: emit .$defaultFn(() => new Date().toISOString()) so Drizzle
// inserts stamp the server-side timestamp automatically. This means callers don't
// need to supply createdAt / updatedAt in INSERT calls — Drizzle fills them in.
const autoSet = field.ownAttr(FIELD_ATTR_AUTO_SET);
const autoSet = field.attr(FIELD_ATTR_AUTO_SET);
const autoSetSuffix = (autoSet === "onCreate" || autoSet === "onUpdate")
? `.$defaultFn(() => new Date().toISOString())`
: "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,12 @@ function renderFieldRules(field: MetaField): string | undefined {
}

// Field-level @required attr (if not already covered by validator).
if (!hasRequired && field.ownAttr(FIELD_ATTR_REQUIRED) === true) {
if (!hasRequired && field.attr(FIELD_ATTR_REQUIRED) === true) {
ruleParts.push(`required: ${JSON.stringify(`${humanize(field.name)} is required`)}`);
}

// Field-level @maxLength attr (if not already covered).
const maxLenAttr = field.ownAttr(FIELD_ATTR_MAX_LENGTH);
const maxLenAttr = field.attr(FIELD_ATTR_MAX_LENGTH);
if (!hasMaxLength && typeof maxLenAttr === "number") {
ruleParts.push(
`maxLength: { value: ${maxLenAttr}, message: ${JSON.stringify(`Must be ${maxLenAttr} characters or fewer`)} }`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function findObject(root: MetaData, name: string): MetaData | undefined {

/** The @objectRef target VO for a nested-object field, or undefined when unresolvable. */
function refVo(field: MetaData, root: MetaData): MetaData | undefined {
const ref = field.ownAttr(FIELD_ATTR_OBJECT_REF);
const ref = field.attr(FIELD_ATTR_OBJECT_REF);
if (typeof ref !== "string") return undefined;
const direct = findObject(root, ref);
if (direct !== undefined) return direct;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function findTemplate(root: MetaData, name: string): MetaData | undefined {

/** The @objectRef target VO for a nested-object field, or undefined when unresolvable. */
function refVo(field: MetaData, root: MetaData): MetaData | undefined {
const ref = field.ownAttr(FIELD_ATTR_OBJECT_REF);
const ref = field.attr(FIELD_ATTR_OBJECT_REF);
if (typeof ref !== "string") return undefined;
const direct = findObject(root, ref);
if (direct !== undefined) return direct;
Expand Down Expand Up @@ -85,7 +85,7 @@ function enumAlias(field: MetaData, ownerName: string): string | undefined {
* required test (boolean `true` only) so the two never skew.
*/
function isFieldRequired(field: MetaData): boolean {
return field.ownAttr(FIELD_ATTR_REQUIRED) === true;
return field.attr(FIELD_ATTR_REQUIRED) === true;
}

/** The mirror→strict mapper name for a value-object (`toStrict<Name>`). */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export function zodTypeFor(field: MetaField): string {
export function currencyMetaFor(field: MetaField): { currency: string; locale: string } | null {
if (field.subType !== FIELD_SUBTYPE_CURRENCY) return null;
const currency =
(field.ownAttr(FIELD_ATTR_CURRENCY) as string | undefined) ?? FIELD_ATTR_CURRENCY_DEFAULT;
(field.attr(FIELD_ATTR_CURRENCY) as string | undefined) ?? FIELD_ATTR_CURRENCY_DEFAULT;
const viewChild = field.views().find((c) => c.subType === VIEW_SUBTYPE_CURRENCY);
const locale =
(viewChild?.ownAttr(VIEW_CURRENCY_ATTR_LOCALE) as string | undefined) ??
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function filterableFields(entity: MetaObject, exclude?: string): MetaField[] {
// fields() returns effective fields, so inherited fields (from extends:/super:) are included in allowlists.
return entity
.fields()
.filter((c) => c.ownAttr(FIELD_ATTR_FILTERABLE) === true && c.name !== exclude);
.filter((c) => c.attr(FIELD_ATTR_FILTERABLE) === true && c.name !== exclude);
}

/**
Expand Down Expand Up @@ -93,7 +93,7 @@ export const ${entity.name}SortAllowlist = {} as const satisfies SortAllowlist;
}
const rows = sortable
.map((f) => {
const defaultOrder = f.ownAttr(FIELD_ATTR_SORTABLE_DEFAULT_ORDER) as string | undefined;
const defaultOrder = f.attr(FIELD_ATTR_SORTABLE_DEFAULT_ORDER) as string | undefined;
const rule =
defaultOrder === "asc" || defaultOrder === "desc"
? `{ defaultOrder: ${JSON.stringify(defaultOrder)} as const }`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import { FIELD_ATTR_FILTERABLE, FIELD_ATTR_SORTABLE } from "@metaobjectsdev/meta
* 3. no @sortable → sortable iff @filterable === true
*/
export function isSortableField(field: MetaField): boolean {
const sortableAttr = field.ownAttr(FIELD_ATTR_SORTABLE);
const sortableAttr = field.attr(FIELD_ATTR_SORTABLE);
if (sortableAttr === true) return true;
if (sortableAttr === false) return false;
return field.ownAttr(FIELD_ATTR_FILTERABLE) === true;
return field.attr(FIELD_ATTR_FILTERABLE) === true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function renderFieldUnion(field: MetaField): string {
export function renderFilterType(entity: MetaObject, exclude?: string): Code {
// fields() returns effective fields, so inherited fields (from extends:/super:) are included in filter types.
const allFields = entity.fields().filter((c) => c.name !== exclude);
const filterableFieldsList = allFields.filter((c) => c.ownAttr(FIELD_ATTR_FILTERABLE) === true);
const filterableFieldsList = allFields.filter((c) => c.attr(FIELD_ATTR_FILTERABLE) === true);
// Sort union uses isSortableField — same predicate as renderSortAllowlist to prevent
// client/server mismatches (@filterable: true + @sortable: false must be excluded from both).
const sortFieldNames = allFields.filter(isSortableField).map((f) => `"${f.name}"`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,21 @@ export function isArray(field: MetaData): boolean {

/** True iff the field's @required is explicitly true (or the string "true"). */
export function isRequired(field: MetaData): boolean {
const v = field.ownAttr(FIELD_ATTR_REQUIRED);
const v = field.attr(FIELD_ATTR_REQUIRED);
if (v === true) return true;
return typeof v === "string" && v.toLowerCase() === "true";
}

/** True iff the field's @xmlText is explicitly true (the XML text-content extract marker). */
export function xmlText(field: MetaData): boolean {
const v = field.ownAttr(FIELD_ATTR_XML_TEXT);
const v = field.attr(FIELD_ATTR_XML_TEXT);
if (v === true) return true;
return typeof v === "string" && v.toLowerCase() === "true";
}

/** The string members of an enum field's @values attr (empty when absent). */
export function enumValues(field: MetaData): string[] {
const v = field.ownAttr(FIELD_ATTR_VALUES);
const v = field.attr(FIELD_ATTR_VALUES);
if (Array.isArray(v)) return v.map((e) => String(e));
return [];
}
Expand All @@ -100,15 +100,15 @@ export function enumValues(field: MetaData): string[] {
* or null when absent. Read own-attr only — `@coerceDefault` is concrete, never inherited.
*/
export function coerceDefault(field: MetaData): string | null {
const v = field.ownAttr(FIELD_ATTR_COERCE_DEFAULT);
const v = field.attr(FIELD_ATTR_COERCE_DEFAULT);
return typeof v === "string" && v.length > 0 ? v : null;
}

/**
* FR-011: the field's `@default` member symbol (absent-fill enum value), or null when absent.
*/
export function defaultValue(field: MetaData): string | null {
const v = field.ownAttr(FIELD_ATTR_DEFAULT);
const v = field.attr(FIELD_ATTR_DEFAULT);
return typeof v === "string" && v.length > 0 ? v : null;
}

Expand All @@ -128,7 +128,7 @@ export function resolveNormalize(field: MetaData, ownerObject: MetaData | null):

/** The `@normalize` attr of a node as a NormalizeMode, or null when absent. */
function normalizeAttrOf(node: MetaData): NormalizeMode | null {
const v = node.ownAttr(FIELD_ATTR_NORMALIZE);
const v = node.attr(FIELD_ATTR_NORMALIZE);
return typeof v === "string" && v.length > 0 ? (v as NormalizeMode) : null;
}

Expand Down
Loading
Loading