Skip to content

Commit 88453ae

Browse files
authored
Merge pull request #3416 from SeedCompany/refactor/db-labels
2 parents 2c021c4 + 5d139c8 commit 88453ae

File tree

10 files changed

+66
-84
lines changed

10 files changed

+66
-84
lines changed

src/common/db-label.decorator.ts

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,9 @@
1-
import { cleanSplit, isNotFalsy } from '@seedcompany/common';
2-
import { uniq } from 'lodash';
3-
4-
export const DbLabelSymbol = Symbol('DbLabelSymbol');
5-
6-
export const DbLabel =
7-
(...labels: string[] | [null]): PropertyDecorator & ClassDecorator =>
8-
(target: any, key?: string | symbol) => {
9-
const prev: string[] =
10-
(key
11-
? Reflect.getMetadata(DbLabelSymbol, target, key)
12-
: Reflect.getMetadata(DbLabelSymbol, target)) ?? [];
13-
14-
const now = uniq(
15-
[
16-
...prev,
17-
// Add labels split by `:`
18-
...labels.flatMap((l) => cleanSplit(l ?? '', ':')),
19-
].filter(isNotFalsy),
20-
);
21-
22-
key
23-
? Reflect.defineMetadata(DbLabelSymbol, now, target, key)
24-
: Reflect.defineMetadata(DbLabelSymbol, now, target);
25-
if (!key) {
26-
return target;
27-
}
28-
};
1+
import { cleanSplit, setOf } from '@seedcompany/common';
2+
import { createMetadataDecorator } from '@seedcompany/nest';
3+
4+
export const DbLabel = createMetadataDecorator({
5+
types: ['class', 'property'],
6+
setter: (...labels: string[] | [null]) =>
7+
setOf(labels.flatMap((label) => cleanSplit(label ?? '', ':'))),
8+
merge: ({ previous, next }) => previous?.union(next) ?? next,
9+
});

src/common/db-label.helpers.ts

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/common/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export * from './create-and-inject';
1515
export * from './data-object';
1616
export * from './date-filter.input';
1717
export { DbLabel } from './db-label.decorator';
18-
export * from './db-label.helpers';
1918
export { DbSort } from './db-sort.decorator';
2019
export * from './db-unique.decorator';
2120
export * from './disabled.decorator';

src/common/resource.dto.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import { Field, InterfaceType } from '@nestjs/graphql';
1+
import { CLASS_TYPE_METADATA, Field, InterfaceType } from '@nestjs/graphql';
2+
import { type ClassType as ClassTypeVal } from '@nestjs/graphql/dist/enums/class-type.enum.js';
23
import {
34
cached,
45
type FnLike,
56
mapValues,
67
setInspectOnClass,
78
setToJson,
89
} from '@seedcompany/common';
10+
import { createMetadataDecorator } from '@seedcompany/nest';
911
import { LazyGetter as Once } from 'lazy-get-decorator';
1012
import { DateTime } from 'luxon';
1113
import { keys as keysOf } from 'ts-transformer-keys';
@@ -20,14 +22,18 @@ import { type ScopedRole } from '../components/authorization/dto';
2022
import { CalculatedSymbol } from './calculated.decorator';
2123
import { DataObject } from './data-object';
2224
import { DbLabel } from './db-label.decorator';
23-
import { getDbClassLabels, getDbPropertyLabels } from './db-label.helpers';
2425
import { ServerException } from './exceptions';
2526
import { type ID, IdField } from './id-field';
2627
import { DateTimeField } from './luxon.graphql';
2728
import { getParentTypes } from './parent-types';
2829
import { type MaybeSecured, type SecuredProps } from './secured-property';
2930
import { type AbstractClassType } from './types';
3031

32+
const GqlClassType = createMetadataDecorator({
33+
key: CLASS_TYPE_METADATA,
34+
setter: (type: ClassTypeVal) => type,
35+
});
36+
3137
const hasTypename = (value: unknown): value is { __typename: string } =>
3238
value != null &&
3339
typeof value === 'object' &&
@@ -282,7 +288,20 @@ export class EnhancedResource<T extends ResourceShape<any>> {
282288

283289
@Once()
284290
get dbLabels() {
285-
return getDbClassLabels(this.type);
291+
const labels = getParentTypes(this.type).flatMap((cls) => {
292+
if (
293+
// Is declared as some gql object. i.e. avoids DataObject.
294+
!GqlClassType.get(cls) ||
295+
// Avoid intersected classes.
296+
// getParentTypes will give us the intersect-ees directly.
297+
cls.name.startsWith('Intersection')
298+
) {
299+
return [];
300+
}
301+
const declared = DbLabel.getOwn(cls);
302+
return declared ? [...declared] : [cls.name];
303+
});
304+
return [...new Set([...labels, 'BaseNode'])];
286305
}
287306
get dbLabel() {
288307
return this.dbLabels[0];
@@ -291,9 +310,10 @@ export class EnhancedResource<T extends ResourceShape<any>> {
291310
get dbPropLabels(): {
292311
readonly [K in keyof T['prototype'] & string]?: readonly string[];
293312
} {
294-
return mapValues.fromList(this.props, (prop) =>
295-
getDbPropertyLabels(this.type, prop),
296-
).asRecord;
313+
return mapValues.fromList(this.props, (prop) => {
314+
const declared = DbLabel.get(this.type, prop as unknown as string);
315+
return [...new Set([...(declared ?? []), 'Property'])];
316+
}).asRecord;
297317
}
298318
}
299319
setInspectOnClass(EnhancedResource, (res) => ({ collapsed }) => {

src/components/changeset/dto/changeset-aware.dto.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Field, InterfaceType } from '@nestjs/graphql';
22
import { stripIndent } from 'common-tags';
3-
import { type ID, IdField } from '~/common';
3+
import { DbLabel, type ID, IdField } from '~/common';
44
import { type BaseNode } from '~/core/database/results';
55
import { Changeset } from './changeset.dto';
66

@@ -11,6 +11,9 @@ import { Changeset } from './changeset.dto';
1111
returned.
1212
`,
1313
})
14+
// Maintaining previous functionality.
15+
// This could be removed (and data migrated) to query it.
16+
@DbLabel(null)
1417
export abstract class ChangesetAware {
1518
@IdField({
1619
description: "The object's ID",

src/components/pin/dto/pinnable.dto.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { Field, InterfaceType } from '@nestjs/graphql';
2-
import { type ID, IdField } from '~/common';
2+
import { DbLabel, type ID, IdField } from '~/common';
33

44
@InterfaceType({
55
description: 'An item that can be pinned',
66
})
7+
// Maintaining previous functionality.
8+
// This could be removed (and data migrated) to query it.
9+
@DbLabel(null)
710
export class Pinnable {
811
@IdField()
912
readonly id: ID;

src/components/product-progress/product-progress.repository.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { stripIndent } from 'common-tags';
33
import { node, type Query, relation } from 'cypher-query-builder';
44
import { DateTime } from 'luxon';
55
import {
6+
EnhancedResource,
67
generateId,
7-
getDbClassLabels,
88
type ID,
99
NotFoundException,
1010
type Session,
@@ -25,15 +25,18 @@ import { PeriodicReportService } from '../periodic-report';
2525
import { ReportType } from '../periodic-report/dto';
2626
import { type ProductStep } from '../product/dto';
2727
import {
28-
ProductProgress,
2928
type ProductProgressInput,
3029
type ProgressVariant,
3130
type ProgressVariantByProductInput,
3231
type ProgressVariantByReportInput,
33-
StepProgress,
32+
ProductProgress as RawProductProgress,
33+
StepProgress as RawStepProgress,
3434
type UnsecuredProductProgress,
3535
} from './dto';
3636

37+
const ProductProgress = EnhancedResource.of(RawProductProgress);
38+
const StepProgress = EnhancedResource.of(RawStepProgress);
39+
3740
@Injectable()
3841
export class ProductProgressRepository {
3942
constructor(
@@ -169,7 +172,7 @@ export class ProductProgressRepository {
169172
.optionalMatch([
170173
node('report'),
171174
relation('out', '', 'progress', ACTIVE),
172-
node('progress', 'ProductProgress', {
175+
node('progress', ProductProgress.dbLabel, {
173176
variant: variable('variant'),
174177
}),
175178
relation('in', '', 'progress', ACTIVE),
@@ -191,7 +194,7 @@ export class ProductProgressRepository {
191194
.match([
192195
node('progress'),
193196
relation('out', '', 'step', ACTIVE),
194-
node('stepNode', 'StepProgress'),
197+
node('stepNode', StepProgress.dbLabel),
195198
])
196199
.apply(matchProps({ nodeName: 'stepNode', outputVar: 'step' }))
197200
.return(collect('step').as('steps')),
@@ -249,7 +252,7 @@ export class ProductProgressRepository {
249252
.merge([
250253
node('product'),
251254
relation('out', 'productProgressRel', 'progress', ACTIVE),
252-
node('progress', 'ProductProgress', {
255+
node('progress', ProductProgress.dbLabel, {
253256
variant: variable('variant'),
254257
}),
255258
relation('in', 'reportProgressRel', 'progress', ACTIVE),
@@ -258,7 +261,7 @@ export class ProductProgressRepository {
258261
.onCreate.set(
259262
{
260263
labels: {
261-
progress: getDbClassLabels(ProductProgress),
264+
progress: ProductProgress.dbLabels,
262265
},
263266
values: {
264267
progress: { id: tempProgressId, createdAt },
@@ -282,14 +285,14 @@ export class ProductProgressRepository {
282285
.merge([
283286
node('progress'),
284287
relation('out', 'progressStepRel', 'step', ACTIVE),
285-
node('stepNode', 'StepProgress', {
288+
node('stepNode', StepProgress.dbLabel, {
286289
step: variable('stepInput.step'),
287290
}),
288291
])
289292
.onCreate.set(
290293
{
291294
labels: {
292-
stepNode: getDbClassLabels(StepProgress),
295+
stepNode: StepProgress.dbLabels,
293296
},
294297
values: {
295298
stepNode: { createdAt },

src/components/product/product.repository.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { DateTime } from 'luxon';
1212
import { type Except, type Merge } from 'type-fest';
1313
import {
1414
CreationFailed,
15-
getDbClassLabels,
15+
EnhancedResource,
1616
type ID,
1717
type Range,
1818
type Session,
@@ -36,7 +36,7 @@ import {
3636
} from '~/core/database/query';
3737
import { ScriptureReferenceRepository } from '../scripture';
3838
import {
39-
ScriptureRange,
39+
ScriptureRange as RawScriptureRange,
4040
type ScriptureRangeInput,
4141
type UnspecifiedScripturePortion,
4242
type UnspecifiedScripturePortionInput,
@@ -60,6 +60,8 @@ import {
6060
type UpdateDirectScriptureProduct,
6161
} from './dto';
6262

63+
const ScriptureRange = EnhancedResource.of(RawScriptureRange);
64+
6365
export type HydratedProductRow = Merge<
6466
Omit<
6567
DbTypeOf<
@@ -109,7 +111,7 @@ export class ProductRepository extends CommonRepository {
109111
.match([
110112
node('node'),
111113
relation('out', '', 'scriptureReferences', ACTIVE),
112-
node('scriptureRanges', 'ScriptureRange'),
114+
node('scriptureRanges', ScriptureRange.dbLabel),
113115
])
114116
.return(
115117
collect('scriptureRanges { .start, .end }').as('scriptureRanges'),
@@ -332,8 +334,8 @@ export class ProductRepository extends CommonRepository {
332334
[
333335
node('node'),
334336
relation('out', '', label, ACTIVE),
335-
node('', getDbClassLabels(ScriptureRange), {
336-
...ScriptureRange.fromReferences(range),
337+
node('', ScriptureRange.dbLabels, {
338+
...ScriptureRange.type.fromReferences(range),
337339
createdAt,
338340
}),
339341
];

src/components/workflow/dto/workflow-event.dto.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Field, ObjectType } from '@nestjs/graphql';
22
import { DateTime } from 'luxon';
33
import {
44
DateTimeField,
5+
DbLabel,
56
type ID,
67
IdField,
78
type MadeEnum,
@@ -18,6 +19,7 @@ export function WorkflowEvent<State extends string>(
1819
transitionType: ReturnType<typeof WorkflowTransition>,
1920
) {
2021
@ObjectType({ isAbstract: true })
22+
@DbLabel('WorkflowEvent')
2123
abstract class WorkflowEventClass {
2224
@IdField()
2325
readonly id: ID;

src/core/database/common.repository.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { DateTime } from 'luxon';
55
import {
66
DbUnique,
77
EnhancedResource,
8-
getDbClassLabels,
98
type ID,
109
InputException,
1110
isIdLike,
@@ -170,9 +169,10 @@ export class CommonRepository {
170169
}
171170

172171
protected getConstraintsFor(resource: ResourceShape<any>) {
172+
const { dbLabel } = EnhancedResource.of(resource);
173173
return [
174174
...(resource.Props.includes('id')
175-
? [createUniqueConstraint(getDbClassLabels(resource)[0], 'id')]
175+
? [createUniqueConstraint(dbLabel, 'id')]
176176
: []),
177177
...resource.Props.flatMap((prop) => {
178178
const label = DbUnique.get(resource, prop);

0 commit comments

Comments
 (0)