Skip to content

Commit a3e6876

Browse files
authored
Merge pull request #3361 from SeedCompany/updates
Bump libs to be compatible with Nest 11 / tweak scalar metadata / improve changeset pipe
2 parents fa56e4b + be3f3e9 commit a3e6876

13 files changed

+433
-179
lines changed

package.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@
4848
"@nestjs/graphql": "^12.0.9",
4949
"@nestjs/platform-fastify": "^10.4.3",
5050
"@patarapolw/prettyprint": "^1.0.3",
51-
"@seedcompany/cache": "^2.0.0",
52-
"@seedcompany/common": ">=0.16 <1",
53-
"@seedcompany/data-loader": "^1.0.0",
54-
"@seedcompany/nest": "^1.1.0",
55-
"@seedcompany/nestjs-email": "^4.0.0",
56-
"@seedcompany/scripture": "^0.3.0",
51+
"@seedcompany/cache": "^3.0.2",
52+
"@seedcompany/common": ">=0.17 <1",
53+
"@seedcompany/data-loader": "^2.0.1",
54+
"@seedcompany/nest": "^1.4.0",
55+
"@seedcompany/nestjs-email": "^4.1.1",
56+
"@seedcompany/scripture": ">=0.8.0",
5757
"argon2": "^0.31.1",
5858
"aws-xray-sdk-core": "^3.5.3",
5959
"chalk": "^5.3.0",

src/app.module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ import { TimeZoneModule } from './components/timezone';
4242
import { UserModule } from './components/user/user.module';
4343
import { CoreModule, LoggerModule } from './core';
4444

45+
import '@seedcompany/nest/patches';
46+
4547
assert(
4648
keys<{ foo: string }>().length === 1,
4749
'Sanity check for key transformer failed',

src/common/mask-secrets.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { isPlainObject } from '@nestjs/common/utils/shared.utils.js';
2-
import { mapValues } from '@seedcompany/common';
1+
import { isPlainObject, mapValues } from '@seedcompany/common';
32

43
export const maskSecrets = (
54
obj: Record<string, any>,

src/common/resource.dto.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { Field, InterfaceType } from '@nestjs/graphql';
2-
import { cached, FnLike, mapValues } from '@seedcompany/common';
2+
import {
3+
cached,
4+
FnLike,
5+
mapValues,
6+
setInspectOnClass,
7+
} from '@seedcompany/common';
38
import { LazyGetter as Once } from 'lazy-get-decorator';
49
import { DateTime } from 'luxon';
510
import { keys as keysOf } from 'ts-transformer-keys';
6-
import { inspect } from 'util';
711
import type {
812
ResourceDBMap,
913
ResourceLike,
@@ -118,10 +122,6 @@ export class EnhancedResource<T extends ResourceShape<any>> {
118122
return cached(EnhancedResource.refs, resource, factory);
119123
}
120124

121-
[inspect.custom]() {
122-
return `${this.constructor.name} { ${this.name} }`;
123-
}
124-
125125
/**
126126
* Check if the given type is the same as this type.
127127
* A helper to narrow the type of enhanced resource.
@@ -292,6 +292,9 @@ export class EnhancedResource<T extends ResourceShape<any>> {
292292
).asRecord;
293293
}
294294
}
295+
setInspectOnClass(EnhancedResource, (res) => ({ collapsed }) => {
296+
return collapsed(res.name, 'Enhanced');
297+
});
295298

296299
export interface EnhancedRelation<TResourceStatic extends ResourceShape<any>> {
297300
readonly name: RelKey<TResourceStatic>;

src/common/rich-text.scalar.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { applyDecorators } from '@nestjs/common';
22
import { Field, FieldOptions, ObjectType } from '@nestjs/graphql';
3+
import { setToStringTag } from '@seedcompany/common';
4+
import { markSkipClassTransformation } from '@seedcompany/nest';
35
import { IsObject } from 'class-validator';
46
import { createHash } from 'crypto';
57
import { GraphQLScalarType } from 'graphql';
@@ -71,6 +73,8 @@ export class RichTextDocument {
7173
return isEqual(aBlocks, bBlocks);
7274
}
7375
}
76+
setToStringTag(RichTextDocument, 'RichText');
77+
markSkipClassTransformation(RichTextDocument);
7478

7579
export const RichTextField = (options?: FieldOptions) =>
7680
applyDecorators(

src/common/temporal/calendar-date.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { setInspectOnClass, setToStringTag } from '@seedcompany/common';
12
import {
23
DateObjectUnits,
34
DateTime,
@@ -12,7 +13,6 @@ import {
1213
ZoneOptions,
1314
} from 'luxon';
1415
import type { DefaultValidity, IfValid } from 'luxon/src/_util';
15-
import { inspect } from 'util';
1616
import { DateInterval } from './date-interval';
1717

1818
/**
@@ -271,9 +271,9 @@ export class CalendarDate<IsValid extends boolean = DefaultValidity>
271271
toPostgres() {
272272
return this.toSQLDate()!;
273273
}
274-
275-
[inspect.custom]() {
276-
const str = this.toLocaleString(DateTime.DATE_SHORT);
277-
return `[Date] ${str}`;
278-
}
279274
}
275+
276+
setInspectOnClass(CalendarDate, (date) => ({ collapsed }) => {
277+
return collapsed(date.toLocaleString(DateTime.DATE_SHORT), 'Date');
278+
});
279+
setToStringTag(CalendarDate, 'CalendarDate');

src/common/temporal/date-interval.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { setInspectOnClass, setToStringTag } from '@seedcompany/common';
12
import {
23
DateInput,
34
DateTimeOptions,
@@ -9,7 +10,6 @@ import {
910
Interval,
1011
IntervalObject,
1112
} from 'luxon';
12-
import { inspect } from 'util';
1313
import { CalendarDate } from './calendar-date';
1414
import '@seedcompany/common/temporal/luxon';
1515

@@ -176,13 +176,18 @@ export class DateInterval
176176
toString(): string {
177177
return `[${this.start.toISO()}${this.end.toISO()}]`;
178178
}
179-
[inspect.custom]() {
180-
return `[Dates ${this.start.toISO()}${this.end.toISO()}]`;
181-
}
182179
expandToFull(unit: DateTimeUnit): DateInterval {
183180
return DateInterval.fromDateTimes(
184181
this.start.startOf(unit),
185182
this.end.endOf(unit),
186183
);
187184
}
188185
}
186+
187+
setInspectOnClass(DateInterval, (i) => ({ collapsed }) => {
188+
return collapsed(`${format(i.start)}${format(i.end)}`, 'Dates');
189+
});
190+
const format = (date: CalendarDate) =>
191+
date.toLocaleString(CalendarDate.DATE_SHORT);
192+
193+
setToStringTag(DateInterval, 'DateInterval');

src/common/temporal/date-time.ts

+34-28
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1+
import { setInspectOnClass, setToStringTag } from '@seedcompany/common';
2+
import { markSkipClassTransformation } from '@seedcompany/nest';
13
import { DateTime } from 'luxon';
24
import * as Neo from 'neo4j-driver';
3-
import { inspect } from 'util';
45

56
/* eslint-disable @typescript-eslint/method-signature-style */
67
declare module 'luxon/src/datetime' {
78
interface DateTime {
89
toNeo4JDate(this: DateTime): Neo.Date<number>;
910
toNeo4JDateTime(this: DateTime): Neo.DateTime<number>;
1011
toPostgres(this: DateTime): string;
11-
[inspect.custom](): string;
1212

1313
// Compatibility with Gel's LocalDate which is a subset of Temporal.PlainDate
1414
get dayOfWeek(): number;
@@ -20,34 +20,40 @@ declare module 'luxon/src/datetime' {
2020
}
2121
/* eslint-enable @typescript-eslint/method-signature-style */
2222

23-
DateTime.prototype.toNeo4JDate = function (this: DateTime) {
24-
return new Neo.types.Date(this.year, this.month, this.day);
25-
};
26-
27-
DateTime.prototype.toNeo4JDateTime = function (this: DateTime) {
28-
return new Neo.types.DateTime(
29-
this.year,
30-
this.month,
31-
this.day,
32-
this.hour,
33-
this.minute,
34-
this.second,
35-
this.millisecond * 1e6,
36-
this.offset * 60,
37-
undefined, // Neo4j doesn't recommend timezone names as they are ambiguous
38-
);
39-
};
40-
41-
DateTime.prototype.toPostgres = function (this: DateTime) {
42-
return this.toSQL();
43-
};
44-
45-
DateTime.prototype[inspect.custom] = function (this: DateTime) {
46-
const str = this.toLocaleString(DateTime.DATETIME_SHORT_WITH_SECONDS);
47-
return `[DateTime] ${str}`;
48-
};
23+
setInspectOnClass(DateTime, (dt) => ({ collapsed }) => {
24+
return collapsed(dt.toLocaleString(DateTime.DATETIME_SHORT_WITH_SECONDS));
25+
});
26+
setToStringTag(DateTime, 'DateTime');
27+
markSkipClassTransformation(DateTime);
4928

5029
Object.defineProperties(DateTime.prototype, {
30+
toNeo4JDate: {
31+
value: function toNeo4JDate(this: DateTime) {
32+
return new Neo.types.Date(this.year, this.month, this.day);
33+
},
34+
},
35+
toNeo4JDateTime: {
36+
value: function toNeo4JDateTime(this: DateTime) {
37+
return new Neo.types.DateTime(
38+
this.year,
39+
this.month,
40+
this.day,
41+
this.hour,
42+
this.minute,
43+
this.second,
44+
this.millisecond * 1e6,
45+
this.offset * 60,
46+
undefined, // Neo4j doesn't recommend timezone names as they're ambiguous
47+
);
48+
},
49+
},
50+
toPostgres: {
51+
value: function toPostgres(this: DateTime) {
52+
return this.toSQL();
53+
},
54+
},
55+
// These below are for compatibility with Gel's LocalDate
56+
// which is a subset of Temporal.PlainDate
5157
dayOfWeek: {
5258
get(this: DateTime) {
5359
return this.weekday;

src/common/temporal/interval.ts

+31-17
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1+
import { setInspectOnClass, setToStringTag } from '@seedcompany/common';
2+
import { markSkipClassTransformation } from '@seedcompany/nest';
13
import { DateTime, DateTimeUnit, Interval } from 'luxon';
24
import { Writable as Mutable } from 'type-fest';
3-
import { inspect } from 'util';
45

56
/* eslint-disable @typescript-eslint/method-signature-style */
67
declare module 'luxon/src/interval' {
78
interface Interval {
8-
[inspect.custom](): string;
9-
109
/**
1110
* Expand this interval to the full duration unit given
1211
*/
@@ -28,25 +27,40 @@ declare module 'luxon/src/interval' {
2827
}
2928
/* eslint-enable @typescript-eslint/method-signature-style */
3029

31-
Interval.prototype[inspect.custom] = function (this: Interval) {
32-
const format = (dt: DateTime) =>
33-
dt.toLocaleString(DateTime.DATETIME_SHORT_WITH_SECONDS);
34-
return `[Interval ${format(this.start)}${format(this.end)})`;
35-
};
30+
setInspectOnClass(Interval, (i: Interval) => ({ stylize }) => {
31+
return (
32+
stylize(`[Interval `, 'special') +
33+
`${format(i.start)}${format(i.end)}` +
34+
stylize(`)`, 'special')
35+
);
36+
});
37+
const format = (dt: DateTime) =>
38+
dt.toLocaleString(DateTime.DATETIME_SHORT_WITH_SECONDS);
3639

37-
Interval.prototype.expandToFull = function (
38-
this: Interval,
39-
unit: DateTimeUnit,
40-
) {
41-
return Interval.fromDateTimes(this.start.startOf(unit), this.end.endOf(unit));
42-
};
40+
setToStringTag(Interval, 'Interval');
41+
markSkipClassTransformation(Interval);
42+
43+
Object.defineProperty(Interval.prototype, 'expandToFull', {
44+
configurable: true,
45+
value: function expandToFull(this: Interval, unit: DateTimeUnit) {
46+
return Interval.fromDateTimes(
47+
this.start.startOf(unit),
48+
this.end.endOf(unit),
49+
);
50+
},
51+
});
4352

44-
const IntervalStatic = Interval as Mutable<typeof Interval>;
53+
const IntervalStatic = new Proxy(Interval, {
54+
set(target, p, value) {
55+
Object.defineProperty(target, p, { value, configurable: true });
56+
return true;
57+
},
58+
}) as Mutable<typeof Interval>;
4559

46-
IntervalStatic.compare = (
60+
IntervalStatic.compare = function compare(
4761
prev: Interval | null | undefined,
4862
now: Interval | null | undefined,
49-
) => {
63+
) {
5064
const removals = !prev ? [] : !now ? [prev] : prev.difference(now);
5165
const additions = !now ? [] : !prev ? [now] : now.difference(prev);
5266
return { removals, additions };

src/components/changeset/enforce-changeset-editable.pipe.ts

+25-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
import { Inject, Injectable, PipeTransform, Scope, Type } from '@nestjs/common';
1+
import {
2+
ArgumentMetadata,
3+
Inject,
4+
Injectable,
5+
PipeTransform,
6+
Scope,
7+
Type,
8+
} from '@nestjs/common';
29
import { CONTEXT } from '@nestjs/graphql';
10+
import { hasCtor, isRegularObject } from '@seedcompany/common';
311
import {
412
DataLoaderContext,
513
DataLoaderStrategy,
614
} from '@seedcompany/data-loader';
7-
import { isPlainObject } from 'lodash';
815
import {
916
ID,
1017
InputException,
@@ -25,6 +32,10 @@ import { shouldValidateEditability } from './validate-editability.decorator';
2532
* This logic is more suited for a Guard or Interceptor, but we don't have that
2633
* information easily accessible at that point.
2734
* Though it could be possible with some work.
35+
*
36+
* Since this is registered as a global/app pipe,
37+
* it is called for every argument of every resolver/controller.
38+
* So we want to be careful to do as little work as possible.
2839
*/
2940
@Injectable({ scope: Scope.REQUEST })
3041
export class EnforceChangesetEditablePipe implements PipeTransform {
@@ -36,12 +47,17 @@ export class EnforceChangesetEditablePipe implements PipeTransform {
3647
private readonly loaderContext: DataLoaderContext,
3748
) {}
3849

39-
async transform(value: any) {
40-
await this.validateRequest(value);
50+
async transform(value: unknown, metadata: ArgumentMetadata) {
51+
await this.validateRequest(value, metadata);
4152
return value;
4253
}
4354

44-
async validateRequest(value: any) {
55+
async validateRequest(value: unknown, metadata: ArgumentMetadata) {
56+
// "body" translates to GQL args
57+
if (metadata.type !== 'body') {
58+
return;
59+
}
60+
4561
const context = isGqlContext(this.context) ? this.context : undefined;
4662
if (context?.operation.operation !== 'mutation') {
4763
return;
@@ -64,8 +80,8 @@ export class EnforceChangesetEditablePipe implements PipeTransform {
6480
}
6581
}
6682

67-
findIdsToValidate(value: any) {
68-
if (!isInstance(value)) {
83+
findIdsToValidate(value: unknown) {
84+
if (!isInputClassInstanceLike(value)) {
6985
return [];
7086
}
7187

@@ -81,5 +97,5 @@ export class EnforceChangesetEditablePipe implements PipeTransform {
8197
}
8298
}
8399

84-
const isInstance = (value: any) =>
85-
value && typeof value === 'object' && !isPlainObject(value);
100+
const isInputClassInstanceLike = (value: unknown): value is object =>
101+
isRegularObject(value) && hasCtor(value);

src/core/database/query-augmentation/pattern-formatting.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isNotFalsy } from '@seedcompany/common';
1+
import { isNotFalsy, isPlainObject } from '@seedcompany/common';
22
import { stripIndent } from 'common-tags';
33
import {
44
Clause,
@@ -16,7 +16,7 @@ import {
1616
} from 'cypher-query-builder';
1717
import type { Term } from 'cypher-query-builder/dist/typings/clauses/term-list-clause';
1818
import type { Parameter } from 'cypher-query-builder/dist/typings/parameter-bag';
19-
import { camelCase, isPlainObject } from 'lodash';
19+
import { camelCase } from 'lodash';
2020
import { isExp } from '../query';
2121
import { WhereAndList } from '../query/where-and-list';
2222

0 commit comments

Comments
 (0)