Skip to content

Commit

Permalink
feat: Added remove transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-crowell committed Aug 1, 2024
1 parent 0c1050c commit a11a631
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 17 deletions.
2 changes: 1 addition & 1 deletion packages/data/src/schema/mutate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { table as entity } from './entity/entity.ts';
/**
* Possible operations to a historical change.
*/
export const mutateOperation = pgEnum('mutate_operation', [ 'create', 'update', 'delete' ]);
export const mutateOperation = pgEnum('mutate_operation', [ 'insert', 'update', 'remove' ]);

/**
* Defines a log of CUD changes to records on other tables in the database.
Expand Down
2 changes: 1 addition & 1 deletion packages/data/src/transaction/audit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getTableName, Table, type TableConfig } from 'drizzle-orm';
import { schema } from '@do-ob/data/schema';

export interface AuditMutationChanges<C extends TableConfig = TableConfig> {
type: 'create' | 'update' | 'delete';
type: 'insert' | 'update' | 'remove';
table: Table<C>,
value: { $id: string, [key: string]: unknown },
}
Expand Down
4 changes: 2 additions & 2 deletions packages/data/src/transaction/insert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ export function insert<
if ($dispatch) {
await tx.transaction(auditMutation($dispatch, [
{
type: 'create',
type: 'insert',
table,
value: typeRecord,
},
{
type: 'create',
type: 'insert',
table: schema.entity,
value: entityRecord,
}
Expand Down
25 changes: 25 additions & 0 deletions packages/data/src/transaction/remove.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { test, expect, beforeAll } from 'vitest';
import { Database, database } from '@do-ob/data/database';
import { seed } from '@do-ob/data/seed';
import { schema } from '@do-ob/data/schema';
import { prepareInput } from '@/test/utility';
import { remove } from './remove';

let db: Database;

beforeAll(async () => {
db = await seed(database());
});

test('remove an entity', async () => {
const input = await prepareInput(db);
const result = await db.transaction(
remove(input, schema.entity, '00000000-0000-0000-0000-000000000000'),
);

expect(result).toEqual([
{
$id: '00000000-0000-0000-0000-000000000000',
},
]);
});
95 changes: 95 additions & 0 deletions packages/data/src/transaction/remove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { Transaction } from './transaction.types';
import { and, eq, SQL, sql, type TableConfig } from 'drizzle-orm';
import type { PgTableWithColumns } from 'drizzle-orm/pg-core';
import { schema } from '@do-ob/data/schema';
import { auditMutation } from './audit';
import { Ambit, type Input } from '@do-ob/core';
import { RowList } from 'postgres';

/**
* Builds an sql filter based on an ambit.
*/
function scope(
$subject: string,
ambit: Ambit,
): SQL {
switch(ambit) {
case Ambit.Global:
return sql`true`;
case Ambit.Owned:
return eq(schema.entity.$owner, $subject);
case Ambit.Created:
return eq(schema.entity.$creator, $subject);
case Ambit.Member:
return sql`false`; // TODO: Implement member scope.
case Ambit.None:
default:
return sql`false`;
}
}

export function remove<
C extends TableConfig,
> (
input: Input,
table: PgTableWithColumns<C>,
$id: string,
) {
return async (tx: Transaction): Promise<[PgTableWithColumns<C>['$inferSelect']]> => {
const { ambit, $subject, $dispatch } = input;

if (!$subject) {
throw new Error('Unauthorized. No subject provided for the update operation.');
}

/**
* Drizzle ORM does not have the `from` clause for the update builder...
* so we have to build the SQL until then.
*
* See issue for updates: https://github.com/drizzle-team/drizzle-orm/issues/2304
*/
const chunks: SQL[] = [];
chunks.push(tx.update(schema.entity).set({ deleted: true }).getSQL());
chunks.push(sql`where ${and(
eq(schema.entity.$id, $id),
scope($subject, ambit),
)}`);
chunks.push(sql`returning *`);
const removeSql = sql.join(chunks, sql.raw(' '));

const { rows } = (await tx.execute(removeSql)) as unknown as { rows: RowList<object[]> };

/**
* Rollback if the update failed or it updated more than one record somehow.
*/
if(rows.length === 0 || rows.length > 1) {
tx.rollback();
}

/**
* Remove this select statement once the `from` method is available in drizzle-orm.
*/
const [ result ] = await tx.select().from(table).where(eq(table.$id, $id)) as [PgTableWithColumns<C>['$inferSelect'] & { $id: string }];

/**
* Rollback if the updated record failed to return anything.
*/
if(!result) {
tx.rollback();
}

if ($dispatch) {
await tx.transaction(auditMutation($dispatch, [
{
type: 'remove',
table,
value: result,
},
]));
}

return [
result,
];
};
}
28 changes: 15 additions & 13 deletions packages/data/src/transaction/update.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Transaction } from './transaction.types';
import { and, eq, getTableColumns, SQL, sql, type TableConfig } from 'drizzle-orm';
import { and, eq, SQL, sql, type TableConfig } from 'drizzle-orm';
import type { PgTableWithColumns } from 'drizzle-orm/pg-core';
import { schema } from '@do-ob/data/schema';
import { auditMutation } from './audit';
Expand Down Expand Up @@ -34,6 +34,11 @@ export function update<
input: Input,
table: PgTableWithColumns<C>,
value: Partial<PgTableWithColumns<C>['$inferSelect']> & { $id: string },

/**
* If true, deleted records will be included in the result.
*/
clairvoyance: boolean = false,
) {
return async (tx: Transaction): Promise<[PgTableWithColumns<C>['$inferSelect']]> => {
const { $id, ...next } = value;
Expand All @@ -44,25 +49,22 @@ export function update<
}

/**
* Drizzle ORM does not have the from method for the update builder...
* so we have to build the SQL manually until then.
* Drizzle ORM does not have the `from` clause for the update builder...
* so we have to build the SQL until then.
*
* See issue for updates: https://github.com/drizzle-team/drizzle-orm/issues/2304
*/
const updateChunks: SQL[] = [];
updateChunks.push(tx.update(table).set(next as object).getSQL());
updateChunks.push(sql`from ${schema.entity}`);
updateChunks.push(sql`where ${and(
const chunks: SQL[] = [];
chunks.push(tx.update(table).set(next as object).getSQL());
chunks.push(sql`from ${schema.entity}`);
chunks.push(sql`where ${and(
eq(table.$id, $id),
eq(table.$id, schema.entity.$id),
eq(table.deleted, clairvoyance),
scope($subject, ambit),
)}`);
updateChunks.push(sql`returning ${
sql.join(
Object.keys(getTableColumns(table)).map((column) => table[column]), sql.raw(', ')
)
}`);
const updateSql = sql.join(updateChunks, sql.raw(' '));
chunks.push(sql`returning *`);
const updateSql = sql.join(chunks, sql.raw(' '));

const { rows } = (await tx.execute(updateSql)) as unknown as { rows: RowList<object[]> };

Expand Down

0 comments on commit a11a631

Please sign in to comment.