UNPKG

@proofkit/fmodata

Version:
176 lines (163 loc) 4.72 kB
import { BaseTable } from "./base-table"; // Helper type to extract schema from BaseTable type ExtractSchema<BT> = BT extends BaseTable<infer S, any, any, any> ? S : never; // Helper type to resolve navigation functions to their return types type ResolveNavigation<T> = { [K in keyof T]: T[K] extends () => infer R ? R : T[K]; }; // Helper to create a getter-based navigation object function createNavigationGetters< Nav extends Record< string, | TableOccurrence<any, any, any, any> | (() => TableOccurrence<any, any, any, any>) >, >(navConfig: Nav): ResolveNavigation<Nav> { const result: any = {}; for (const key in navConfig) { Object.defineProperty(result, key, { get() { const navItem = navConfig[key]; return typeof navItem === "function" ? navItem() : navItem; }, enumerable: true, configurable: true, }); } return result as ResolveNavigation<Nav>; } export class TableOccurrence< BT extends BaseTable<any, any, any, any> = any, Name extends string = string, Nav extends Record< string, | TableOccurrence<any, any, any, any> | (() => TableOccurrence<any, any, any, any>) > = {}, DefSelect extends | "all" | "schema" | readonly (keyof ExtractSchema<BT>)[] = "schema", > { public readonly name: Name; public readonly baseTable: BT; protected _navigationConfig: Nav; public readonly navigation: ResolveNavigation<Nav>; public readonly defaultSelect: DefSelect; public readonly fmtId?: `FMTID:${string}`; constructor(config: { readonly name: Name; readonly baseTable: BT; readonly navigation?: Nav; readonly defaultSelect?: DefSelect; readonly fmtId?: `FMTID:${string}`; }) { this.name = config.name; this.baseTable = config.baseTable; this._navigationConfig = (config.navigation ?? {}) as Nav; this.defaultSelect = (config.defaultSelect ?? "schema") as DefSelect; this.fmtId = config.fmtId; // Create navigation getters that lazily resolve functions this.navigation = createNavigationGetters(this._navigationConfig); } addNavigation< NewNav extends Record< string, | TableOccurrence<any, any, any, any> | (() => TableOccurrence<any, any, any, any>) >, >(nav: NewNav): TableOccurrence<BT, Name, Nav & NewNav, DefSelect> { return new TableOccurrence({ name: this.name, baseTable: this.baseTable, navigation: { ...this._navigationConfig, ...nav } as Nav & NewNav, defaultSelect: this.defaultSelect, fmtId: this.fmtId, }); } /** * Returns the FileMaker table occurrence ID (FMTID) if available, or the table name. * @returns The FMTID string or the table name */ getTableId(): string { return this.fmtId ?? this.name; } /** * Returns the table occurrence name. * @returns The table name */ getTableName(): string { return this.name; } /** * Returns true if this TableOccurrence is using FileMaker table occurrence IDs. */ isUsingTableId(): boolean { return this.fmtId !== undefined; } } // Helper function to create TableOccurrence with proper type inference export function createTableOccurrence< const Name extends string, BT extends BaseTable<any, any, any, any>, DefSelect extends | "all" | "schema" | readonly (keyof ExtractSchema<BT>)[] = "schema", >(config: { name: Name; baseTable: BT; defaultSelect?: DefSelect; fmtId?: `FMTID:${string}`; }): TableOccurrence<BT, Name, {}, DefSelect> { return new TableOccurrence(config); } /** * Creates a TableOccurrence with proper TypeScript type inference. * * Use this function instead of `new TableOccurrence()` to ensure * field names are properly typed. * * @example Without entity IDs * ```ts * const users = defineTableOccurrence({ * name: "users", * baseTable: usersBase, * }); * ``` * * @example With entity IDs * ```ts * const products = defineTableOccurrence({ * name: "products", * baseTable: productsBase, * fmtId: "FMTID:12345", * }); * ``` */ export function defineTableOccurrence< const Name extends string, BT extends BaseTable<any, any, any, any>, const DefSelect extends | "all" | "schema" | readonly (keyof ExtractSchema<BT>)[] = "schema", >(config: { readonly name: Name; readonly baseTable: BT; readonly fmtId?: `FMTID:${string}`; readonly defaultSelect?: DefSelect; readonly navigation?: Record< string, | TableOccurrence<any, any, any, any> | (() => TableOccurrence<any, any, any, any>) >; }): TableOccurrence<BT, Name, {}, DefSelect> { return new TableOccurrence(config) as TableOccurrence< BT, Name, {}, DefSelect >; }