Skip to content

Commit

Permalink
feat(refs): support direct declarations of Refs
Browse files Browse the repository at this point in the history
  • Loading branch information
uladkasach committed Dec 2, 2024
1 parent 4cdcb6f commit 64ccc94
Show file tree
Hide file tree
Showing 25 changed files with 1,153 additions and 121 deletions.
360 changes: 307 additions & 53 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,14 @@
"postinstall": "[ -d .git ] && npx husky install || exit 0"
},
"dependencies": {
"@ehmpathy/error-fns": "1.3.1",
"@ehmpathy/error-fns": "1.3.7",
"@ehmpathy/uni-time": "1.7.4",
"@oclif/core": "3.26.6",
"@oclif/plugin-help": "6.0.22",
"chalk": "2.4.2",
"change-case": "4.1.1",
"domain-objects": "0.22.1",
"domain-objects-metadata": "0.7.4",
"domain-objects-metadata": "0.7.5",
"fast-glob": "3.2.2",
"joi": "17.4.0",
"lodash.omit": "4.5.0",
Expand Down
12 changes: 12 additions & 0 deletions src/domain/objects/SqlSchemaReferenceMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,19 @@ import { DomainObjectReferenceMetadata } from 'domain-objects-metadata';
import Joi from 'joi';

export enum SqlSchemaReferenceMethod {
/**
* .what = when a domain literal is directly nested in another domain object
*/
DIRECT_BY_NESTING = 'DIRECT_BY_NESTING',

/**
* .what = when a domain object is explicitly referenced, via Ref<>, in another domain object
*/
DIRECT_BY_DECLARATION = 'DIRECT_BY_DECLARATION',

/**
* .what = when a domain object is implicitly referenced, via uuid, in another domain object
*/
IMPLICIT_BY_UUID = 'IMPLICIT_BY_UUID',
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { DomainEntity, Ref } from 'domain-objects';
import { Carriage } from './Carriage';

/**
* .what = cargo which has been allocated to a carriage at a given point in time
*/
export interface CarriageCargo {
id?: number;
uuid?: string;

/**
* the itinerary during which it is allocated
*/
itineraryUuid: string; // todo: ref to an itinerary once we declare it

/**
* the carriage it has been allocated to
*/
carriageRef: Ref<typeof Carriage>;

/**
* the slot that it has been allocated to on the carriage
*/
slot: number;

/**
* the cargo that has been allocated
*/
cargoExid: null | string;
}
export class CarriageCargo extends DomainEntity<CarriageCargo> implements CarriageCargo {
public static primary = ['uuid'] as const;
public static unique = ['itineraryUuid', 'carriageRef', 'slot'] as const;
public static updatable = ['cargoExid'];
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './AsyncTaskPredictStationCongestion'
export * from './CarriageCargo';
export * from './Carriage';
export * from './Certificate';
export * from './Engineer';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('getAllPathsMatchingGlobs', () => {
expect(files).toContain('src/domain/objects/Train.ts');
expect(files).toContain('src/domain/objects/TrainLocatedEvent.ts');
expect(files).toContain('src/domain/objects/Station.ts');
expect(files.length).toEqual(14);
expect(files.length).toEqual(15);
});
it('should return paths that match each glob', async () => {
const files = await getAllPathsMatchingGlobs({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,254 @@ export const upsert = async (
GeneratedCodeFile {
"content": "import { withExpectOutput } from 'procedure-fns';
import { findById } from './findById';
import { findByUnique } from './findByUnique';
import { findByUuid } from './findByUuid';
import { findByRef } from './findByRef';
import { upsert } from './upsert';
export const carriageCargoDao = {
findById: withExpectOutput(findById),
findByUnique: withExpectOutput(findByUnique),
findByUuid: withExpectOutput(findByUuid),
findByRef: withExpectOutput(findByRef),
upsert,
};
// include an alias, for improved ease of access via autocomplete
export const daoCarriageCargo = carriageCargoDao;",
"relpath": "carriageCargoDao/index.ts",
},
GeneratedCodeFile {
"content": "import { HasMetadata } from 'type-fns';
import { CarriageCargo } from '$PATH_TO_DOMAIN_OBJECT';
import { SqlQueryFindCarriageByIdOutput, SqlQueryFindCarriageCargoByIdOutput } from '$PATH_TO_GENERATED_SQL_TYPES';
import { castFromDatabaseObject as castCarriageFromDatabaseObject } from '../carriageDao/castFromDatabaseObject';
export const castFromDatabaseObject = (
dbObject: SqlQueryFindCarriageCargoByIdOutput,
): HasMetadata<CarriageCargo> =>
new CarriageCargo({
id: dbObject.id,
uuid: dbObject.uuid,
itineraryUuid: dbObject.itinerary_uuid,
carriageRef: dbObject.carriage_ref,
slot: dbObject.slot,
cargoUuid: dbObject.cargo_uuid,
}) as HasMetadata<CarriageCargo>;",
"relpath": "carriageCargoDao/castFromDatabaseObject.ts",
},
GeneratedCodeFile {
"content": "import { HasMetadata } from 'type-fns';
import { DatabaseConnection } from '$PATH_TO_DATABASE_CONNECTION';
import { log } from '$PATH_TO_LOG_OBJECT';
import { CarriageCargo } from '$PATH_TO_DOMAIN_OBJECT';
import { sqlQueryFindCarriageCargoById } from '$PATH_TO_GENERATED_SQL_QUERY_FUNCTIONS';
import { castFromDatabaseObject } from './castFromDatabaseObject';
export const sql = \`
-- query_name = find_carriage_cargo_by_id
SELECT
carriage_cargo.id,
carriage_cargo.uuid,
carriage_cargo.itinerary_uuid,
carriage_cargo.carriage_ref,
carriage_cargo.slot,
carriage_cargo.cargo_uuid
FROM view_carriage_cargo_hydrated AS carriage_cargo
WHERE carriage_cargo.id = :id;
\`;
export const findById = async (
{
id,
}: {
id: number;
},
context: { dbConnection: DatabaseConnection },
): Promise<HasMetadata<CarriageCargo> | null> => {
const results = await sqlQueryFindCarriageCargoById({
dbExecute: context.dbConnection.query,
logDebug: log.debug,
input: {
id,
},
});
const [dbObject, ...moreDbObjects] = results;
if (moreDbObjects.length) throw new Error('expected only one db object for this query');
if (!dbObject) return null;
return castFromDatabaseObject(dbObject);
};",
"relpath": "carriageCargoDao/findById.ts",
},
GeneratedCodeFile {
"content": "import { HasMetadata } from 'type-fns';
import { DatabaseConnection } from '$PATH_TO_DATABASE_CONNECTION';
import { log } from '$PATH_TO_LOG_OBJECT';
import { CarriageCargo } from '$PATH_TO_DOMAIN_OBJECT';
import { sqlQueryFindCarriageCargoByUnique } from '$PATH_TO_GENERATED_SQL_QUERY_FUNCTIONS';
import { castFromDatabaseObject } from './castFromDatabaseObject';
export const sql = \`
-- query_name = find_carriage_cargo_by_unique
SELECT
carriage_cargo.id,
carriage_cargo.uuid,
carriage_cargo.itinerary_uuid,
carriage_cargo.carriage_ref,
carriage_cargo.slot,
carriage_cargo.cargo_uuid
FROM view_carriage_cargo_hydrated AS carriage_cargo
JOIN view_carriage_cargo_current ON carriage_cargo.id = view_carriage_cargo_current.id
WHERE 1=1
AND carriage_cargo.itinerary_uuid = :itineraryUuid
AND view_carriage_cargo_current.carriage_id = (SELECT id FROM carriage WHERE carriage.uuid = :carriageUuid)
AND carriage_cargo.slot = :slot;
\`;
export const findByUnique = async (
{
itineraryUuid,
carriageRef,
slot,
}: {
itineraryUuid: string;
carriageRef: Carriage;
slot: number;
},
context: { dbConnection: DatabaseConnection },
): Promise<HasMetadata<CarriageCargo> | null> => {
const results = await sqlQueryFindCarriageCargoByUnique({
dbExecute: context.dbConnection.query,
logDebug: log.debug,
input: {
itineraryUuid,
carriageUuid: isPrimaryKeyRef({ of: Carriage })(carriageRef) ? carriageRef.uuid : (await carriageDao.findByRef({ ref: carriageRef }, context).expect('isPresent')).uuid,
slot,
},
});
const [dbObject, ...moreDbObjects] = results;
if (moreDbObjects.length) throw new Error('expected only one db object for this query');
if (!dbObject) return null;
return castFromDatabaseObject(dbObject);
};",
"relpath": "carriageCargoDao/findByUnique.ts",
},
GeneratedCodeFile {
"content": "import { HasMetadata } from 'type-fns';
import { DatabaseConnection } from '$PATH_TO_DATABASE_CONNECTION';
import { log } from '$PATH_TO_LOG_OBJECT';
import { CarriageCargo } from '$PATH_TO_DOMAIN_OBJECT';
import { sqlQueryFindCarriageCargoByUuid } from '$PATH_TO_GENERATED_SQL_QUERY_FUNCTIONS';
import { castFromDatabaseObject } from './castFromDatabaseObject';
export const sql = \`
-- query_name = find_carriage_cargo_by_uuid
SELECT
carriage_cargo.id,
carriage_cargo.uuid,
carriage_cargo.itinerary_uuid,
carriage_cargo.carriage_ref,
carriage_cargo.slot,
carriage_cargo.cargo_uuid
FROM view_carriage_cargo_hydrated AS carriage_cargo
WHERE carriage_cargo.uuid = :uuid;
\`;
export const findByUuid = async (
{
uuid,
}: {
uuid: string;
},
context: { dbConnection: DatabaseConnection },
): Promise<HasMetadata<CarriageCargo> | null> => {
const results = await sqlQueryFindCarriageCargoByUuid({
dbExecute: context.dbConnection.query,
logDebug: log.debug,
input: {
uuid,
},
});
const [dbObject, ...moreDbObjects] = results;
if (moreDbObjects.length) throw new Error('expected only one db object for this query');
if (!dbObject) return null;
return castFromDatabaseObject(dbObject);
};",
"relpath": "carriageCargoDao/findByUuid.ts",
},
GeneratedCodeFile {
"content": "import { UnexpectedCodePathError } from '@ehmpathy/error-fns';
import { Ref, isPrimaryKeyRef, isUniqueKeyRef } from 'domain-objects';
import { HasMetadata } from 'type-fns';
import { CarriageCargo } from '$PATH_TO_DOMAIN_OBJECT';
import { DatabaseConnection } from '$PATH_TO_DATABASE_CONNECTION';
import { findByUnique } from './findByUnique';
import { findByUuid } from './findByUuid';
export const findByRef = async (
input: { ref: Ref<typeof CarriageCargo> },
context: { dbConnection: DatabaseConnection },
): Promise<HasMetadata<CarriageCargo> | null> => {
if (isPrimaryKeyRef({ of: CarriageCargo })(input.ref))
return await findByUuid(input.ref, context);
if (isUniqueKeyRef({ of: CarriageCargo })(input.ref))
return await findByUnique(input.ref, context);
throw new UnexpectedCodePathError('invalid ref for CarriageCargo', { input });
};",
"relpath": "carriageCargoDao/findByRef.ts",
},
GeneratedCodeFile {
"content": "import { HasMetadata } from 'type-fns';
import { DatabaseConnection } from '$PATH_TO_DATABASE_CONNECTION';
import { CarriageCargo } from '$PATH_TO_DOMAIN_OBJECT';
import { log } from '$PATH_TO_LOG_OBJECT';
import { sqlQueryUpsertCarriageCargo } from '$PATH_TO_GENERATED_SQL_QUERY_FUNCTIONS';
export const sql = \`
-- query_name = upsert_carriage_cargo
SELECT
dgv.id, dgv.uuid
FROM upsert_carriage_cargo(
:itineraryUuid,
(SELECT id FROM carriage WHERE carriage.uuid = :carriageUuid),
:slot,
(SELECT id FROM carriage_cargo WHERE carriage_cargo.uuid = :cargoUuid)
) as dgv;
\`;
export const upsert = async (
{
carriageCargo,
}: {
carriageCargo: CarriageCargo;
},
context: { dbConnection: DatabaseConnection },
): Promise<HasMetadata<CarriageCargo>> => {
const results = await sqlQueryUpsertCarriageCargo({
dbExecute: context.dbConnection.query,
logDebug: log.debug,
input: {
itineraryUuid: carriageCargo.itineraryUuid,
carriageUuid: isPrimaryKeyRef({ of: Carriage })(carriageCargo.carriageRef) ? carriageCargo.carriageRef.uuid : (await carriageDao.findByRef({ ref: carriageCargo.carriageRef }, context).expect('isPresent')).uuid,
slot: carriageCargo.slot,
cargoUuid: carriageCargo.cargoUuid,
},
});
const { id, uuid } = results[0]!; // grab the db generated values
return new CarriageCargo({ ...carriageCargo, id, uuid }) as HasMetadata<CarriageCargo>;
};",
"relpath": "carriageCargoDao/upsert.ts",
},
GeneratedCodeFile {
"content": "import { withExpectOutput } from 'procedure-fns';
import { findById } from './findById';
import { findByUnique } from './findByUnique';
import { upsert } from './upsert';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`defineDaoUtilCastMethodCodeForDomainObject should look correct for a domain entity which directly declares a reference to another 1`] = `
"import { HasMetadata } from 'type-fns';
import { CarriageCargo } from '$PATH_TO_DOMAIN_OBJECT';
import { SqlQueryFindCarriageByIdOutput, SqlQueryFindCarriageCargoByIdOutput } from '$PATH_TO_GENERATED_SQL_TYPES';
import { castFromDatabaseObject as castCarriageFromDatabaseObject } from '../carriageDao/castFromDatabaseObject';
export const castFromDatabaseObject = (
dbObject: SqlQueryFindCarriageCargoByIdOutput,
): HasMetadata<CarriageCargo> =>
new CarriageCargo({
id: dbObject.id,
uuid: dbObject.uuid,
carriageRef: { uuid: dbObject.carriage_uuid },
}) as HasMetadata<CarriageCargo>;"
`;
exports[`defineDaoUtilCastMethodCodeForDomainObject should look correct for a domain event with a static referenced array 1`] = `
"import { HasMetadata } from 'type-fns';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`defineQuerySelectExpressionForSqlSchemaProperty reference: direct_by_declaration should define the select expression correctly for a solo DIRECT_BY_DECLARATION reference 1`] = `
"(
SELECT carriage.uuid
FROM carriage WHERE carriage.id = carriage_cargo.carriage_id
) AS carriage_uuid"
`;

exports[`defineQuerySelectExpressionForSqlSchemaProperty reference: direct_by_nesting should define the select expression correctly for a solo DIRECT_BY_NESTING reference 1`] = `
"(
SELECT json_build_object(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,15 @@ export const sql = \`
.map(({ domainObject: domainObjectProperty }) =>
!domainObjectProperty
? null
: `${sqlSchemaName}.${snakeCase(domainObjectProperty.name)}`,
: `${sqlSchemaName}.${
isDomainObjectReferenceProperty(domainObjectProperty) &&
domainObjectProperty.of.extends ===
DomainObjectVariant.DOMAIN_ENTITY // if its a DIRECT_BY_DECLARATION reference, then replace the name; // todo: upgrade to selecting the full ref-by-unique instead and leverage that to stop renaming adhoc to _uuid
? snakeCase(domainObjectProperty.name)
.replace(/_refs$/, '_uuids')
.replace(/_ref$/, '_uuid')
: snakeCase(domainObjectProperty.name)
}`,
)
.filter(isPresent)
.join(',\n ')}
Expand Down
Loading

0 comments on commit 64ccc94

Please sign in to comment.