Source

contracts/crud/serialized-crud-contract.ts

import { FabricCrudContract } from "./crud-contract";
import { Model } from "@decaf-ts/decorator-validation";
import { Context as Ctx, Transaction } from "fabric-contract-api";
import { Constructor } from "@decaf-ts/decoration";
import { Condition, OrderDirection } from "@decaf-ts/core";
import { SerializationError } from "@decaf-ts/db-decorators";

/**
 * @description CRUD contract variant that serializes/deserializes payloads
 * @summary Exposes the same CRUD operations as FabricCrudContract but takes and returns JSON strings to facilitate simple client interactions.
 * @template M - Model type handled by this contract
 * @param {string} name - The contract name
 * @param {Constructor<M>} clazz - The model constructor used to instantiate models from JSON
 * @return {void}
 * @class SerializedCrudContract
 * @example
 * const contract = new SerializedCrudContract<MyModel>('MyModelContract', MyModel);
 * // Client submits JSON string payloads and receives JSON string responses
 */
export class SerializedCrudContract<
  M extends Model,
> extends FabricCrudContract<M> {
  constructor(name: string, clazz: Constructor<M>) {
    super(name, clazz);
  }

  @Transaction()
  override async create(ctx: Ctx, model: string): Promise<string> {
    return this.serialize((await super.create(ctx as any, model)) as M);
  }

  @Transaction(false)
  override async read(ctx: Ctx, key: string): Promise<string> {
    return this.serialize((await super.read(ctx as any, key)) as M);
  }

  @Transaction()
  override async update(ctx: Ctx, model: string): Promise<string> {
    return this.serialize((await super.update(ctx, model)) as M);
  }

  @Transaction()
  override async delete(ctx: Ctx, key: string): Promise<string> {
    return this.serialize((await super.delete(ctx as any, key)) as M);
  }

  @Transaction()
  override async deleteAll(ctx: Ctx, keys: string): Promise<string> {
    const parsedKeys: string[] = JSON.parse(keys);
    return JSON.stringify(
      ((await super.deleteAll(ctx as any, parsedKeys)) as M[]).map(
        (m) => this.serialize(m) as string
      )
    );
  }

  @Transaction(false)
  override async readAll(ctx: Ctx, keys: string): Promise<string> {
    const parsedKeys: string[] = JSON.parse(keys);
    return JSON.stringify(
      ((await super.readAll(ctx as any, parsedKeys)) as M[]).map((m) =>
        this.serialize(m)
      )
    );
  }

  @Transaction()
  override async updateAll(ctx: Ctx, models: string): Promise<string> {
    const list: string[] = JSON.parse(models);
    const modelList: M[] = list
      .map((m) => this.deserialize(m))
      .map((m) => new this.clazz(m));

    return JSON.stringify(
      ((await super.updateAll(ctx as any, modelList)) as M[]).map(
        (m) => this.serialize(m) as string
      )
    );
  }

  @Transaction(false)
  override async statement(ctx: Ctx, method: string, args: string) {
    try {
      args = JSON.parse(args);
    } catch (e: unknown) {
      throw new SerializationError(`Invalid args: ${e}`);
    }
    if (!Array.isArray(args))
      throw new SerializationError(
        `Invalid args: ${JSON.stringify(args)}. must be an array`
      );
    return JSON.stringify(await super.statement(ctx, method, ...args));
  }

  @Transaction(false)
  override async listBy(ctx: Ctx, key: string, order: string) {
    return JSON.stringify(
      await super.listBy(ctx, key as keyof M, order as OrderDirection)
    );
  }

  @Transaction(false)
  override async paginateBy(
    ctx: Ctx,
    key: string,
    order: string,
    ref: string
  ): Promise<string> {
    try {
      ref = JSON.parse(ref);
    } catch (e: unknown) {
      throw new SerializationError(
        `Failed to deserialize paginateBy reference: ${e}`
      );
    }
    return JSON.stringify(
      await super.paginateBy(ctx, key, order as any, ref as any)
    );
  }

  @Transaction(false)
  override async findOneBy(ctx: Ctx, key: string, value: string) {
    return JSON.stringify(await super.findOneBy(ctx, key, value));
  }

  // @Transaction(false)
  override async query(
    ctx: Ctx,
    condition: string,
    orderBy: string,
    order: string,
    limit?: number,
    skip?: number
  ): Promise<string> {
    let cond: Condition<any>;
    try {
      cond = Condition.from(JSON.parse(condition));
    } catch (e: unknown) {
      throw new SerializationError(`Invalid condition: ${e}`);
    }

    return JSON.stringify(
      await super.query(ctx, cond, orderBy, order as any, limit, skip)
    );
  }
  //
  // // @Transaction(false)
  // override async raw(
  //   context: Ctx,
  //   rawInput: string,
  //   docsOnly: boolean,
  //   ...args: string[]
  // ): Promise<any> {
  //   const { ctx } = await this.logCtx([context], this.raw);
  //   const parsedInput: MangoQuery = JSON.parse(rawInput);
  //   return JSON.stringify(await super.raw(ctx, parsedInput, docsOnly, ...args));
  // }

  @Transaction()
  override async init(ctx: Ctx): Promise<void> {
    await super.init(ctx);
  }

  @Transaction(false)
  override async healthcheck(ctx: Ctx): Promise<string> {
    //TODO: TRIM NOT WORKING CHECK LATER
    return JSON.stringify(await super.healthcheck(ctx as any));
  }

  @Transaction()
  override async createAll(context: Ctx, models: string): Promise<string> {
    const list: string[] = JSON.parse(models);
    const modelList: M[] = list
      .map((m) => this.deserialize(m))
      .map((m) => new this.clazz(m));

    const result = (await super.createAll(context, modelList)) as M[];
    return JSON.stringify(result.map((m) => this.serialize(m) as string));
  }
}