import { CollectionSchemaBase, SchemaBase, Field } from './dsl.js'; import type { FrontAppSchema, ProjectApiSchema } from './project.js'; import { TableSchema } from './db.js'; import type { EntitySchema } from './entity.js'; import type { FilterSchema } from './filter.js'; import type { SetExpr } from './expr.js'; /** A data-access layer bound to one frontend app, or to the api-level common * domain layer (app unset — shared across modules). */ export interface DaoSchema extends CollectionSchemaBase { type: 'dao'; /** The backend api module this DAO belongs to (shared instance from * project.config.ts apis). DAOs are always backend-side, so storage is * dao_schema/{api.name}/{app.name}/dao/ — app unset = the api-level common * domain layer, stored at dao_schema/{api.name}/common/dao/. */ api: ProjectApiSchema; /** The frontend app this DAO belongs to (shared instance from project.config). * Unset = api-level common domain DAO shared by all modules of the api. */ app?: FrontAppSchema; /** The table this DAO operates on (single-table atomicity). */ table: TableSchema; /** Methods keyed by name — the map key is written back as the method name. */ methods: Record; } /** Method input for defineDao: name is written back from the methods map key. */ export type DaoMethodDef = Omit | Omit | Omit | Omit | Omit | Omit | Omit; /** The tenant column of the dao table when the app declares a tenant. * Deterministic name derivation: `{tenant.phrase}_{tenant.pk}` (e.g. shop * with pk id → `shop_id`) — checked directly against the table columns, * no FK traversal. Tables without that column are global tables (valid: * system config tables carry no tenant id). Exported for generator/linter. */ export declare function tenantFkOf(dao: DaoSchema): Field | undefined; export declare function defineDao(options: { name: string; api: ProjectApiSchema; app?: FrontAppSchema; table: TableSchema; methods: Record; description?: string; }): DaoSchema; /** Sort specification for find results. */ export interface OrderBySchema { /** Column to sort by. */ column: Field; /** Sort direction. */ sort: 'asc' | 'desc'; } /** Query rows by criteria; returns a list of row objects. */ export interface FindSchema extends SchemaBase { type: 'find'; schema: DaoSchema; /** Query filter; omit = all rows. */ args?: FilterSchema; /** Pagination marker: 'page' (page/pageSize) or 'limit' (position/limit). * Only presence matters — parameter shapes are a generator convention. */ mode?: 'page' | 'limit'; /** Result sort; omit = no explicit order. */ orderBy?: OrderBySchema | OrderBySchema[]; /** Result row: selected columns (multi-table via external reference columns). */ results: EntitySchema; } /** Fetch a single row by exact key — any column or AND combination of * columns (single PK, composite PK, getByXX are all legal declarations). * May miss the row: the generated signature returns Row | null. */ export interface GetSchema extends SchemaBase { type: 'get'; schema: DaoSchema; /** Key column(s): one field or AND-exact-matched fields. */ args: Field | Field[]; /** Additional criteria AND-combined onto the key (e.g. state condition * for optimistic reads). */ where?: FilterSchema; /** Result row: selected columns (multi-table via external reference columns). */ results: EntitySchema; } /** Insert one row; returns the generated key (string). */ export interface InsertSchema extends SchemaBase { type: 'insert'; schema: DaoSchema; /** Row object. */ args: EntitySchema; } /** Update one row; returns number (affected rows). The where key is derived * from the PK columns inside args; tenant/version columns are extracted * from the row into WHERE and never SET (optimistic lock auto-manages * `version = version + 1`). */ export interface UpdateSchema extends SchemaBase { type: 'update'; schema: DaoSchema; /** Row object containing the PK columns plus the columns to set. */ args: EntitySchema; /** Expression-set columns (`col = expr`) beyond direct assignment. */ set?: SetExpr[]; /** Additional criteria beyond the derived PK (AND-combined). */ where?: FilterSchema; } /** Delete one row by exact key; returns number (affected rows). */ export interface DeleteSchema extends SchemaBase { type: 'delete'; schema: DaoSchema; /** Key column(s): one field or AND-exact-matched fields. */ args: Field | Field[]; } /** Insert-or-update (MySQL ON DUPLICATE KEY UPDATE): atomic idempotent write. * Returns affected rows (1 = inserted, 2 = updated, 0 = unchanged). * No id generation — every conflict key must be carried in args. Tables with * an auto-increment pk are forbidden (MySQL auto_increment burns ids on * duplicate-key updates). */ export interface UpsertSchema extends SchemaBase { type: 'upsert'; schema: DaoSchema; /** Row object: must carry every conflict key plus the columns to merge. */ args: EntitySchema; /** Conflict keys: must equal the pk or a complete unique index column set. */ keys: Field | Field[]; } export type DaoMethodSchema = FindSchema | GetSchema | InsertSchema | UpdateSchema | DeleteSchema | UpsertSchema | AggregateSchema; /** Aggregate query (find upgraded at the select level): the result entity * mixes plain columns (the GROUP BY dimensions) and aggregate fields * (aggField — count/sum/avg). * * Usage — aggregate fields are defined in the entity file, the dao method * only references the entity: * * ```ts * // entity_schema/{api}/{app}/entity/order.entity.ts * export const orderStatusStats = defineEntity({ * name: 'OrderStatusStats', * api, app, * columns: [ * order.columns.status, // GROUP BY dimension * aggField('total', Compute.count()), // count → jsType 'number' * aggField('sumAmt', Compute.sum(order.columns.amount)), // sum(decimal) → 'string' * ], * }); * * // dao_schema/{api}/{app}/dao/order.dao.ts * stats: { type: 'aggregate', args: orderFilter, results: orderStatusStats }, * ``` */ export interface AggregateSchema extends SchemaBase { type: 'aggregate'; schema: DaoSchema; /** Criteria filter, same shape as find. */ args?: FilterSchema; /** Result entity: plain columns group the rows (one row per group); * aggregate fields become the computed output columns. The entity — with * its aggField definitions — lives in the entity file ({table}.entity.ts); * the dao method only references it, never defines aggregate fields inline. */ results: EntitySchema; }