import { Repository, Sequence } from "@decaf-ts/core";
import type { ContextOf, FlagsOf, MaybeContextualArg } from "@decaf-ts/core";
import { Model } from "@decaf-ts/decorator-validation";
import { Constructor } from "@decaf-ts/decoration";
import type { FabricClientAdapter } from "./FabricClientAdapter";
import {
Context,
OperationKeys,
enforceDBDecorators,
ValidationError,
InternalError,
reduceErrorsToPrint,
PrimaryKeyType,
} from "@decaf-ts/db-decorators";
/**
* @description Repository implementation for Fabric client operations
* @summary Extends the generic Repository to prepare context and arguments for CRUD operations executed via a Fabric client Adapter, wiring RepositoryFlags and Fabric-specific overrides.
* @template M extends Model - The model type handled by this repository
* @param {Adapter<any, MangoQuery, FabricFlags, Context<FabricFlags>>} [adapter] - Optional adapter instance used to execute operations
* @param {Constructor<M>} [clazz] - Optional model constructor used by the repository
* @return {void}
* @class FabricClientRepository
* @example
* import { Repository } from "@decaf-ts/core";
* import { FabricClientRepository } from "@decaf-ts/for-fabric";
*
* class User extends Model { id!: string; name!: string; }
* const repo = new FabricClientRepository<User>();
* const created = await repo.create(new User({ id: "1", name: "Alice" }));
* const loaded = await repo.read("1");
*
* @mermaid
* sequenceDiagram
* participant App
* participant Repo as FabricClientRepository
* participant Adapter
* App->>Repo: create(model)
* Repo->>Repo: createPrefix(model, ...args)
* Repo->>Adapter: create(table, id, model, flags)
* Adapter-->>Repo: result
* Repo-->>App: model
*/
export class FabricClientRepository<M extends Model> extends Repository<
M,
FabricClientAdapter
> {
override _overrides?: Partial<FlagsOf<FabricClientAdapter>> = {
ignoreValidation: true,
ignoreHandlers: true,
};
constructor(adapter?: FabricClientAdapter, clazz?: Constructor<M>) {
super(adapter, clazz);
}
protected override async createPrefix(
model: M,
...args: MaybeContextualArg<ContextOf<FabricClientAdapter>>
): Promise<[M, ...any[], ContextOf<FabricClientAdapter>]> {
const contextArgs = await Context.args<M, ContextOf<FabricClientAdapter>>(
OperationKeys.CREATE,
this.class,
args,
this.adapter,
this._overrides || {}
);
const shouldRunHandlers =
contextArgs.context.get("ignoreHandlers") !== true;
const shouldValidate = !contextArgs.context.get("ignoreValidation");
model = new this.class(model);
if (shouldRunHandlers)
await enforceDBDecorators<M, Repository<M, FabricClientAdapter>, any>(
this,
contextArgs.context,
model,
OperationKeys.CREATE,
OperationKeys.ON
);
if (shouldValidate) {
const errors = await Promise.resolve(
model.hasErrors(
...(contextArgs.context.get("ignoredValidationProperties") || [])
)
);
if (errors) throw new ValidationError(errors.toString());
}
return [model, ...contextArgs.args];
}
protected override async createAllPrefix(
models: M[],
...args: MaybeContextualArg<ContextOf<FabricClientAdapter>>
): Promise<[M[], ...any[], ContextOf<FabricClientAdapter>]> {
const contextArgs = await Context.args<M, ContextOf<FabricClientAdapter>>(
OperationKeys.CREATE,
this.class,
args,
this.adapter,
this._overrides || {}
);
const shouldRunHandlers =
contextArgs.context.get("ignoreHandlers") !== true;
const shouldValidate = !contextArgs.context.get("ignoreValidation");
if (!models.length) return [models, ...contextArgs.args];
const opts = Model.sequenceFor(models[0]);
let ids: (string | number | bigint | undefined)[] = [];
if (opts.type) {
if (!opts.name) opts.name = Sequence.pk(models[0]);
ids = await (
await this.adapter.Sequence(opts)
).range(models.length, ...contextArgs.args);
} else {
ids = models.map((m, i) => {
if (typeof m[this.pk] === "undefined")
throw new InternalError(
`Primary key is not defined for model in position ${i}`
);
return m[this.pk] as string;
});
}
models = await Promise.all(
models.map(async (m, i) => {
m = new this.class(m);
if (opts.type) {
m[this.pk] = (
opts.type !== "String"
? ids[i]
: opts.generated
? ids[i]
: `${m[this.pk]}`.toString()
) as M[keyof M];
}
if (shouldRunHandlers)
await enforceDBDecorators<M, Repository<M, FabricClientAdapter>, any>(
this,
contextArgs.context,
m,
OperationKeys.CREATE,
OperationKeys.ON
);
return m;
})
);
if (shouldValidate) {
const ignoredProps =
contextArgs.context.get("ignoredValidationProperties") || [];
const errors = await Promise.all(
models.map((m) => Promise.resolve(m.hasErrors(...ignoredProps)))
);
const errorMessages = reduceErrorsToPrint(errors);
if (errorMessages) throw new ValidationError(errorMessages);
}
return [models, ...contextArgs.args];
}
protected override async readPrefix(
key: PrimaryKeyType,
...args: MaybeContextualArg<ContextOf<FabricClientAdapter>>
): Promise<[PrimaryKeyType, ...any[], ContextOf<FabricClientAdapter>]> {
const contextArgs = await Context.args<M, ContextOf<FabricClientAdapter>>(
OperationKeys.READ,
this.class,
args,
this.adapter,
this._overrides || {}
);
const model: M = new this.class();
model[this.pk] = key as M[keyof M];
await enforceDBDecorators<M, Repository<M, FabricClientAdapter>, any>(
this,
contextArgs.context,
model,
OperationKeys.READ,
OperationKeys.ON
);
return [key, ...contextArgs.args];
}
protected override async readAllPrefix(
keys: PrimaryKeyType[],
...args: MaybeContextualArg<ContextOf<FabricClientAdapter>>
): Promise<[PrimaryKeyType[], ...any[], ContextOf<FabricClientAdapter>]> {
const contextArgs = await Context.args<M, ContextOf<FabricClientAdapter>>(
OperationKeys.READ,
this.class,
args,
this.adapter,
this._overrides || {}
);
await Promise.all(
keys.map(async (k) => {
const m = new this.class();
m[this.pk] = k as M[keyof M];
return enforceDBDecorators<M, Repository<M, FabricClientAdapter>, any>(
this,
contextArgs.context,
m,
OperationKeys.READ,
OperationKeys.ON
);
})
);
return [keys, ...contextArgs.args];
}
protected override async updatePrefix(
model: M,
...args: MaybeContextualArg<ContextOf<FabricClientAdapter>>
): Promise<[M, ...args: any[], ContextOf<FabricClientAdapter>]> {
const contextArgs = await Context.args(
OperationKeys.UPDATE,
this.class,
args,
this.adapter,
this._overrides || {}
);
const shouldRunHandlers =
contextArgs.context.get("ignoreHandlers") !== true;
const shouldValidate = !contextArgs.context.get("ignoreValidation");
const pk = model[this.pk] as string;
if (!pk)
throw new InternalError(
`No value for the Id is defined under the property ${this.pk as string}`
);
const oldModel = await this.read(pk, ...contextArgs.args);
model = Model.merge(oldModel, model, this.class);
if (shouldRunHandlers)
await enforceDBDecorators(
this,
contextArgs.context as any,
model,
OperationKeys.UPDATE,
OperationKeys.ON,
oldModel
);
if (shouldValidate) {
const errors = await Promise.resolve(
model.hasErrors(
oldModel,
...Model.relations(this.class),
...(contextArgs.context.get("ignoredValidationProperties") || [])
)
);
if (errors) throw new ValidationError(errors.toString());
}
return [model, ...contextArgs.args];
}
override async update(
model: M,
...args: MaybeContextualArg<ContextOf<FabricClientAdapter>>
): Promise<M> {
const { ctxArgs, log, ctx } = this.logCtx(args, this.update);
// eslint-disable-next-line prefer-const
let { record, id, transient } = this.adapter.prepare(model, ctx);
log.debug(
`updating ${this.class.name} in table ${Model.tableName(this.class)} with id ${id}`
);
record = await this.adapter.update(this.class, id, record, ...ctxArgs);
return this.adapter.revert<M>(record, this.class, id, transient, ctx);
}
}
Source