Skip to content
This repository was archived by the owner on Jun 12, 2026. It is now read-only.
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
| --------------------- | --------------------------------------------------------------------------------------- |
| English only | All code must be in English: variable names, function names, comments |
| PNPM only | Always use pnpm, never npm or yarn |
| NO COMMENTS | Code must be self-documenting through clear naming. TSDoc is allowed for public APIs |
| NO COMMENTS | Code must be self-documenting through clear naming. TSDoc is allowed for public APIs |
| NO switch/case | Use objects, maps, or arrays instead |
| NO inline types | Define proper interfaces/types, never use anonymous types like `{a: string, b: number}` |
| Functions ≀ 40 lines | Split into subfunctions if longer |
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

A module that provides data-api interfaces only, without implementation code.


## Installation

```bash
Expand All @@ -22,9 +21,9 @@ ajs project modules add @antelopejs/data-api

This module implements the Data API interface it provides a standardized way to interact with data sources in your application. The interface is installed separately to maintain modularity and minimize dependencies.

| Name | Install command | |
| ------------- | --------------------------------------- | ----------------------------------------------------------------- |
| Data API | `ajs module imports add data-api` | [Documentation](https://github.com/AntelopeJS/interface-data-api) |
| Name | Install command | |
| -------- | --------------------------------- | ----------------------------------------------------------------- |
| Data API | `ajs module imports add data-api` | [Documentation](https://github.com/AntelopeJS/interface-data-api) |

## License

Expand Down
12 changes: 6 additions & 6 deletions output/data-api/beta/components.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RequestContext } from '@ajs/api/beta';
import { Database, Datum, Stream, Table, ValueProxy } from '@ajs/database/beta';
import { Datum, Stream, Table, ValueProxy, SchemaInstance } from '@ajs/database/beta';
import { DataModel } from '@ajs/database-decorators/beta/model';
import { DataAPIMeta, FilterValue } from './metadata';
export declare namespace Parameters {
Expand Down Expand Up @@ -54,19 +54,19 @@ export declare namespace Query {
function GetModel(obj: any, meta: DataAPIMeta): InstanceType<DataModel> & {
constructor: DataModel;
};
function Foreign(db: Database, meta: DataAPIMeta, query: Stream, pluck?: Set<string>): Stream;
function Foreign(db: Database, meta: DataAPIMeta, query: Datum, pluck?: Set<string>): Datum;
function Foreign(db: SchemaInstance<any>, meta: DataAPIMeta, query: Stream<any>, pluck?: Set<string>): Stream<any>;
function Foreign(db: SchemaInstance<any>, meta: DataAPIMeta, query: Datum<any>, pluck?: Set<string>): Datum<any>;
function ReadProperties(obj: any, meta: DataAPIMeta, dbData: any, action?: string, onlyList?: boolean): Promise<Record<string, any>>;
function WriteProperties(obj: any, meta: DataAPIMeta, bodyData: Record<string, any>, action?: string, existingDBData?: Record<string, any>): Promise<Record<string, any>>;
function Get(table: Table, id: string | ValueProxy.Proxy<string>, index?: string): import("@ajs/database/beta").SingleSelection<any>;
function Get(table: Table<any>, id: string | ValueProxy<string>, index?: string): Datum<any>;
function List<T extends Record<string, any>>(obj: any, meta: DataAPIMeta, request: Table<T>, reqCtx: RequestContext, sorting?: [string, 'asc' | 'desc' | undefined], filters?: Record<string, FilterValue>): [sorted: Stream<T>, total: Datum<number>];
function Delete(table: Table, id: string | string[]): import("@ajs/database/beta").Query<import("@ajs/database/beta").Result.Write<any>>;
function Delete(table: Table<any>, id: string | string[]): import("@ajs/database/beta").Query<number>;
}
export declare namespace Validation {
function MandatoryFields(meta: DataAPIMeta, obj: any, type: string): void;
function ValidateTypes(meta: DataAPIMeta, obj: Record<string, any>): Promise<void>;
function Lock(obj: any, meta: DataAPIMeta, data: any): void;
function Unlock(obj: any, meta: DataAPIMeta, dbData: any): void;
function UnlockRequest<T extends {}, K extends keyof T>(obj: any, meta: DataAPIMeta, row: ValueProxy.Proxy<T>, field: K): ValueProxy.Proxy<T[K]>;
function UnlockRequest<T extends {}, K extends keyof T>(obj: any, meta: DataAPIMeta, row: ValueProxy<T>, field: K): ValueProxy<T[K]>;
function ClearInternal(meta: DataAPIMeta, obj: Record<string, any> | Array<Record<string, any>>): void;
}
10 changes: 5 additions & 5 deletions output/data-api/beta/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export type ExtractDefCallbacks<T extends {}> = {
declare abstract class TableHolder<C extends Class> {
table: InstanceType<C>;
}
export declare function DataController<C extends Class, P extends DataControllerDef = DataControllerDef, Base extends ControllerClass = ControllerClass>(tableClass: C, def: P, base: Base, schemaName?: string): Class<ExtractDefCallbacks<P> & TableHolder<C>> & Base;
export declare function DataController<C extends Class, P extends DataControllerDef = DataControllerDef, Base extends ControllerClass = ControllerClass>(tableClass: C, def: P, base: Base): Class<ExtractDefCallbacks<P> & TableHolder<C>> & Base;
export declare const RegisterDataController: () => import("@ajs/core/beta/decorators").ClassDecorator<Class<any, any[]>>;
export declare function GetDataControllerMeta(thisObj: any): DataAPIMeta;
export declare namespace DefaultRoutes {
Expand All @@ -42,7 +42,7 @@ export declare namespace DefaultRoutes {
method: string;
};
const New: {
func: (reqCtx: RequestContext, params: Parameters.NewParameters, body: Buffer) => Promise<string[] | undefined>;
func: (reqCtx: RequestContext, params: Parameters.NewParameters, body: Buffer) => Promise<string[]>;
args: (import("@ajs/core/beta/decorators").PropertyDecorator & ParameterDecorator)[];
method: string;
};
Expand All @@ -52,7 +52,7 @@ export declare namespace DefaultRoutes {
method: string;
};
const Delete: {
func: (reqCtx: RequestContext, params: Parameters.DeleteParameters) => Promise<import("@ajs/database/beta").Result.Write<any>>;
func: (reqCtx: RequestContext, params: Parameters.DeleteParameters) => Promise<number>;
args: (import("@ajs/core/beta/decorators").PropertyDecorator & ParameterDecorator)[];
method: string;
};
Expand All @@ -73,7 +73,7 @@ export declare namespace DefaultRoutes {
method: string;
};
readonly new: {
func: (reqCtx: RequestContext, params: Parameters.NewParameters, body: Buffer) => Promise<string[] | undefined>;
func: (reqCtx: RequestContext, params: Parameters.NewParameters, body: Buffer) => Promise<string[]>;
args: (import("@ajs/core/beta/decorators").PropertyDecorator & ParameterDecorator)[];
method: string;
};
Expand All @@ -83,7 +83,7 @@ export declare namespace DefaultRoutes {
method: string;
};
readonly delete: {
func: (reqCtx: RequestContext, params: Parameters.DeleteParameters) => Promise<import("@ajs/database/beta").Result.Write<any>>;
func: (reqCtx: RequestContext, params: Parameters.DeleteParameters) => Promise<number>;
args: (import("@ajs/core/beta/decorators").PropertyDecorator & ParameterDecorator)[];
method: string;
};
Expand Down
4 changes: 2 additions & 2 deletions output/data-api/beta/metadata.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Class } from '@ajs/core/beta/decorators';
import { RequestContext } from '@ajs/api/beta';
import { ValueProxy } from '@ajs/database/beta';
import { ValueProxy, ValueProxyOrValue } from '@ajs/database/beta';
import { DataControllerCallbackWithOptions } from '.';
import { ContainerModifier } from '@ajs/database-decorators/beta/modifiers/common';
import { Table } from '@ajs/database-decorators/beta';
Expand Down Expand Up @@ -68,7 +68,7 @@ export type FilterValue = [value: string, mode: Comparison];
*/
export type FilterFunction<T extends Record<string, any>, U extends Record<string, any> = Record<string, any>> = (context: RequestContext & {
this: T;
}, proxy: ValueProxy.Proxy<any>, key: string, value: FilterValue[0], mode: FilterValue[1], row: ValueProxy.Proxy<U>) => ValueProxy.ProxyOrVal<boolean>;
}, proxy: ValueProxy<any>, key: string, value: FilterValue[0], mode: FilterValue[1], row: ValueProxy<U>) => ValueProxyOrValue<boolean>;
export interface ReadableAccessFields {
getters: [string, FieldData][];
props: [string, FieldData][];
Expand Down
21 changes: 12 additions & 9 deletions playground/antelope.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
},
"@antelopejs/api": {
"source": {
"type": "package",
"package": "@antelopejs/api",
"version": "0.0.1"
"type": "git",
"remote": "https://github.com/AntelopeJS/api.git",
"branch": "main",
"installCommand": ["pnpm i", "npx tsc"]
},
"config": {
"servers": [
Expand All @@ -35,19 +36,21 @@
},
"@antelopejs/mongodb": {
"source": {
"type": "package",
"package": "@antelopejs/mongodb",
"version": "0.0.1"
"type": "git",
"remote": "https://github.com/AntelopeJS/mongodb.git",
"branch": "main",
"installCommand": ["pnpm i", "npx tsc"]
},
"config": {
"url": "mongodb://localhost:27017"
}
},
"@antelopejs/database-decorators": {
"source": {
"type": "package",
"package": "@antelopejs/database-decorators",
"version": "0.0.1"
"type": "git",
"remote": "https://github.com/AntelopeJS/database-decorators.git",
"branch": "main",
"installCommand": ["pnpm i", "npx tsc"]
}
}
},
Expand Down
6 changes: 2 additions & 4 deletions playground/src/data-api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { User, UserModel } from '../db/user';
import { Controller } from '@ajs/api/beta';
import { StaticModel } from '@ajs/database-decorators/beta';
import { Access, AccessMode, Listable, Mandatory, ModelReference, Sortable } from '@ajs/data-api/beta/metadata';
import { databaseName } from '../utils';

const routes = {
get: DefaultRoutes.Get,
list: DefaultRoutes.List,
Expand All @@ -14,9 +12,9 @@ const routes = {
};

@RegisterDataController()
export class UserDataAPI extends DataController(User, routes, Controller('/users'), 'default') {
export class UserDataAPI extends DataController(User, routes, Controller('/users')) {
@ModelReference()
@StaticModel(UserModel, databaseName)
@StaticModel(UserModel, 'default')
declare userModel: UserModel;

@Listable()
Expand Down
3 changes: 2 additions & 1 deletion playground/src/db/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BasicDataModel, Index, Table, RegisterTable, Fixture } from '@ajs/database-decorators/beta';
import { schemaName } from '../utils';

const tableName = 'users';

Expand Down Expand Up @@ -27,7 +28,7 @@ const tableName = 'users';
{ _id: 'user19', email: 'user19@example.com', firstName: 'Andrew', lastName: 'Harris' },
{ _id: 'user20', email: 'user20@example.com', firstName: 'Stephanie', lastName: 'Clark' },
])
@RegisterTable(tableName, 'default')
@RegisterTable(tableName, schemaName)
export class User extends Table {
@Index({ primary: true })
declare _id: string;
Expand Down
12 changes: 4 additions & 8 deletions playground/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { Logging } from '@ajs/logging/beta';
import { InitializeDatabaseFromSchema } from '@ajs/database-decorators/beta';
import { databaseName } from './utils';
import { CreateDatabaseSchemaInstance } from '@ajs/database-decorators/beta';
import { schemaName } from './utils';
import './db/user';
import './data-api/user';

export function construct(config: unknown): void {
// Set things up when module is loaded
Logging.Debug('DATA-API module playground initialized with config:' + JSON.stringify(config));
}
export function construct(): void {}

export async function start(): Promise<void> {
await InitializeDatabaseFromSchema(databaseName, 'default');
await CreateDatabaseSchemaInstance(schemaName, 'default');
}

export function destroy(): void {}
Expand Down
2 changes: 1 addition & 1 deletion playground/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const databaseName = 'data-api-playground';
export const schemaName = 'data-api-playground';
66 changes: 39 additions & 27 deletions src/interfaces/data-api/beta/components.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MakeParameterAndPropertyDecorator } from '@ajs/core/beta/decorators';
import { RequestContext, SetParameterProvider } from '@ajs/api/beta';
import { Database, Datum, Stream, Table, ValueProxy } from '@ajs/database/beta';
import { Datum, Stream, Table, ValueProxy, SchemaInstance } from '@ajs/database/beta';
import { DataModel } from '@ajs/database-decorators/beta/model';
import { assert } from '@ajs/api-util/beta';
import { DataAPIMeta, FilterValue } from './metadata';
Expand All @@ -9,8 +9,8 @@
import { Constructible } from '@ajs/database-decorators/beta/common';

export namespace Parameters {
export function GetOptionOverrides<T extends Record<string, any>>(reqCtx: RequestContext): T {

Check warning on line 12 in src/interfaces/data-api/beta/components.ts

View workflow job for this annotation

GitHub Actions / ubuntu

Unexpected any. Specify a different type
return (<any>reqCtx).dataAPIEntry?.options ?? {};

Check warning on line 13 in src/interfaces/data-api/beta/components.ts

View workflow job for this annotation

GitHub Actions / ubuntu

Unexpected any. Specify a different type
}

export function ExtractFilters(reqCtx: RequestContext, meta: DataAPIMeta): Record<string, FilterValue> {
Expand All @@ -33,10 +33,10 @@
string: (val: string) => val,
};
type ConvertersKey = keyof typeof converters;
type GenericParams<T extends Record<string, any>> = {

Check warning on line 36 in src/interfaces/data-api/beta/components.ts

View workflow job for this annotation

GitHub Actions / ubuntu

Unexpected any. Specify a different type
[K in keyof T]: ConvertersKey | `multi:${ConvertersKey}` | ((reqCtx: RequestContext, meta: DataAPIMeta) => any);

Check warning on line 37 in src/interfaces/data-api/beta/components.ts

View workflow job for this annotation

GitHub Actions / ubuntu

Unexpected any. Specify a different type
};
export function ExtractGeneric<T extends Record<string, any>>(

Check warning on line 39 in src/interfaces/data-api/beta/components.ts

View workflow job for this annotation

GitHub Actions / ubuntu

Unexpected any. Specify a different type
reqCtx: RequestContext,
meta: DataAPIMeta,
dynamic: GenericParams<Partial<T>>,
Expand All @@ -49,17 +49,17 @@
if (typeof extractor === 'string') {
if (extractor.startsWith('multi:')) {
const converter = converters[<ConvertersKey>extractor.substring(6)];
result[key as keyof T] = reqCtx.url.searchParams

Check warning on line 52 in src/interfaces/data-api/beta/components.ts

View workflow job for this annotation

GitHub Actions / ubuntu

Unsafe assignment of an `any` value
.getAll(key)
.map((searchVal) => converter(searchVal)) as any;

Check warning on line 54 in src/interfaces/data-api/beta/components.ts

View workflow job for this annotation

GitHub Actions / ubuntu

Unexpected any. Specify a different type
} else {
const searchVal = reqCtx.url.searchParams.get(key);
if (searchVal !== null) {
result[key as keyof T] = converters[<ConvertersKey>extractor](searchVal) as any;

Check warning on line 58 in src/interfaces/data-api/beta/components.ts

View workflow job for this annotation

GitHub Actions / ubuntu

Unexpected any. Specify a different type

Check warning on line 58 in src/interfaces/data-api/beta/components.ts

View workflow job for this annotation

GitHub Actions / ubuntu

Unsafe assignment of an `any` value
}
}
} else {
result[key as keyof T] = extractor(reqCtx, meta);

Check warning on line 62 in src/interfaces/data-api/beta/components.ts

View workflow job for this annotation

GitHub Actions / ubuntu

Unsafe assignment of an `any` value
}
}
}
Expand Down Expand Up @@ -171,50 +171,62 @@
return obj[meta.modelKey];
}

export function Foreign(db: Database, meta: DataAPIMeta, query: Stream, pluck?: Set<string>): Stream;
export function Foreign(db: Database, meta: DataAPIMeta, query: Datum, pluck?: Set<string>): Datum;
export function Foreign(db: Database, meta: DataAPIMeta, query: Stream | Datum, pluck?: Set<string>): Stream | Datum {
export function Foreign(
db: SchemaInstance<any>,
meta: DataAPIMeta,
query: Stream<any>,
pluck?: Set<string>,
): Stream<any>;
export function Foreign(
db: SchemaInstance<any>,
meta: DataAPIMeta,
query: Datum<any>,
pluck?: Set<string>,
): Datum<any>;
export function Foreign(
db: SchemaInstance<any>,
meta: DataAPIMeta,
query: Stream<any> | Datum<any>,
pluck?: Set<string>,
): Stream<any> | Datum<any> {
/**/
if (query.lookup) {
if (query instanceof Stream) {
for (const [name, field] of Object.entries(meta.fields)) {
if (!field.foreign || (pluck && !pluck.has(name))) {
continue;
}
const [table, _tableClass, index, _multi, pluckField] = field.foreign;
let other: Stream = db.table(table);
let other = db.table(table);
if (pluckField) {
other = other.pluck('_internal', ...pluckField);
query = query.lookup(other.pluck('_internal', ...pluckField) as Table<any>, name, index || '_id');
} else {
query = query.lookup(other, name, index || '_id');
}
query = query.lookup(other, name, index || '_id');
}
return query;
} else {
const oldForeign = (obj: ValueProxy.Proxy<Record<string, any>>) => {
const oldForeign = (obj: ValueProxy<Record<string, any>>) => {
const changedFields: Record<string, any> = {};
for (const [name, field] of Object.entries(meta.fields)) {
if (!field.foreign || (pluck && !pluck.has(name))) {
continue;
}
const [table, _tableClass, index, multi, pluckField] = field.foreign;
if (multi) {
changedFields[name] = (obj(name) as ValueProxy.Proxy<string[]>).default([]).map((val) => {
let foreignObject: Datum = Get(db.table(table), val, index);
changedFields[name] = (obj.key(name) as ValueProxy<string[]>).default([]).map((val) => {
let foreignObject: Datum<any> = Get(db.table(table), val, index);
if (pluckField) {
foreignObject = foreignObject.pluck('_internal', ...pluckField);
}
return foreignObject.default(null);
});
} else {
changedFields[name] = Get(db.table(table), obj(name) as ValueProxy.Proxy<string>, index).default(null);
changedFields[name] = Get(db.table(table), obj.key(name) as ValueProxy<string>, index).default(null);
}
}
return obj.merge(changedFields);
};
if ('map' in query) {
return query.map(oldForeign as ValueProxy.Proxy<Record<string, any>>);
} else {
return query.do(oldForeign as ValueProxy.Proxy<Record<string, any>>);
}
return query.do(oldForeign);
}
}

Expand Down Expand Up @@ -274,8 +286,8 @@
return dbData;
}

export function Get(table: Table, id: string | ValueProxy.Proxy<string>, index?: string) {
return index ? table.getAll(index, id).nth(0) : table.get(id);
export function Get(table: Table<any>, id: string | ValueProxy<string>, index?: string) {
return index ? table.getAll(id as string, index).nth(0) : table.get(id as string);
}

export function List<T extends Record<string, any>>(
Expand All @@ -290,11 +302,11 @@
const indexFilter = filterList.find(([name]) => filters![name][1] === 'eq' && meta.fields[name]?.indexable)?.[0];
const index = indexFilter ? meta.fields[indexFilter].dbName || indexFilter : undefined;

let tmpRequest = index ? request.getAll(index, filters![indexFilter!][0]) : request;
let tmpRequest = index ? request.getAll(filters![indexFilter!][0], index) : request;

const shouldSort = sorting && meta.fields[sorting[0]]?.sortable;
if (shouldSort && shouldSort.indexed) {
tmpRequest = tmpRequest.orderBy(sorting[0] as keyof T, sorting[1] ?? 'asc', false);
tmpRequest = tmpRequest.orderBy(sorting[0], sorting[1] ?? 'asc');
}
if (filterList.length > 0) {
// TODO: rework for modifier-affected fields (unlockrequest)
Expand All @@ -309,20 +321,20 @@
name,
filters![name][0],
filters![name][1],
row as ValueProxy.Proxy<Record<string, any>>,
row as ValueProxy<Record<string, any>>,
),
),
tmpRequest,
);
}
if (shouldSort && !shouldSort.indexed) {
tmpRequest = tmpRequest.orderBy(sorting[0] as keyof T, sorting[1] ?? 'asc', true);
tmpRequest = tmpRequest.orderBy(sorting[0], sorting[1] ?? 'asc');
}
return [tmpRequest, tmpRequest.count()];
}

export function Delete(table: Table, id: string | string[]) {
return Array.isArray(id) ? table.getAll(undefined!, ...id).delete() : table.get(id).delete();
export function Delete(table: Table<any>, id: string | string[]) {
return Array.isArray(id) ? table.getAll(id).delete() : table.get(id).delete();
}
}

Expand Down Expand Up @@ -373,9 +385,9 @@
export function UnlockRequest<T extends {}, K extends keyof T>(
obj: any,
meta: DataAPIMeta,
row: ValueProxy.Proxy<T>,
row: ValueProxy<T>,
field: K,
): ValueProxy.Proxy<T[K]> {
): ValueProxy<T[K]> {
const modifiers = Array.from(meta.modifierKeys.entries()).map(([modifier, field]) => ({
modifier,
args: [obj[field]],
Expand Down
Loading