@pcg/core 1.0.0-alpha.0 → 1.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/index.d.ts +21 -1189
  2. package/dist/index.js +76 -1867
  3. package/dist/index.js.map +1 -1
  4. package/package.json +16 -3
  5. package/.turbo/turbo-build.log +0 -15
  6. package/CHANGELOG.md +0 -7
  7. package/src/abstracts/index.ts +0 -3
  8. package/src/abstracts/nestjs-resource-service.ts +0 -154
  9. package/src/abstracts/nestjs-service.ts +0 -25
  10. package/src/configs/app.config.ts +0 -185
  11. package/src/configs/db.config.ts +0 -122
  12. package/src/configs/index.ts +0 -4
  13. package/src/configs/logger.config.ts +0 -62
  14. package/src/context/action-context.ts +0 -34
  15. package/src/context/current-user.ts +0 -49
  16. package/src/context/index.ts +0 -5
  17. package/src/context/platform-method-context.ts +0 -5
  18. package/src/context/service-method-context.ts +0 -47
  19. package/src/db/snake-naming.strategy.ts +0 -277
  20. package/src/enums/app-env.enum.ts +0 -36
  21. package/src/enums/app-mode.enum.ts +0 -5
  22. package/src/enums/app-server.enum.ts +0 -39
  23. package/src/enums/index.ts +0 -4
  24. package/src/enums/worker-mode.enum.ts +0 -11
  25. package/src/errors/access-denied.error.ts +0 -18
  26. package/src/errors/bad-request.error.ts +0 -9
  27. package/src/errors/forbidden.error.ts +0 -9
  28. package/src/errors/index.ts +0 -8
  29. package/src/errors/input-validation.error.ts +0 -16
  30. package/src/errors/nest-error.filter.ts +0 -70
  31. package/src/errors/nest-error.ts +0 -63
  32. package/src/errors/not-found.error.ts +0 -9
  33. package/src/errors/unauthorized.error.ts +0 -9
  34. package/src/exceptions/http-exception-response.ts +0 -34
  35. package/src/exceptions/http-exceptions.filter.ts +0 -95
  36. package/src/index.ts +0 -32
  37. package/src/jwt/extractors.ts +0 -80
  38. package/src/jwt/types.ts +0 -209
  39. package/src/logger/classes/logger-factory.ts +0 -54
  40. package/src/logger/classes/logger.ts +0 -340
  41. package/src/logger/classes/nest-system-logger.ts +0 -63
  42. package/src/logger/classes/typeorm-logger.ts +0 -83
  43. package/src/logger/index.ts +0 -20
  44. package/src/logger/logger.constants.ts +0 -24
  45. package/src/logger/logger.interfaces.ts +0 -98
  46. package/src/logger/logger.module.ts +0 -45
  47. package/src/logger/logger.providers.ts +0 -140
  48. package/src/logger/winston.tools.ts +0 -241
  49. package/src/middlewares/app.middleware.ts +0 -26
  50. package/src/middlewares/index.ts +0 -1
  51. package/src/modules/hooks/base-hook.ts +0 -64
  52. package/src/modules/hooks/decorators/on-hook.decorator.ts +0 -19
  53. package/src/modules/hooks/hooks.module.ts +0 -10
  54. package/src/modules/hooks/hooks.service.ts +0 -28
  55. package/src/modules/hooks/index.ts +0 -11
  56. package/src/modules/id/id.module.ts +0 -26
  57. package/src/modules/id/id.service.ts +0 -57
  58. package/src/modules/id/index.ts +0 -2
  59. package/src/modules/postgres-pubsub/index.ts +0 -3
  60. package/src/modules/postgres-pubsub/postgres-pubsub.module.ts +0 -14
  61. package/src/modules/postgres-pubsub/postgres-pubsub.ts +0 -461
  62. package/src/pagination/constants.ts +0 -9
  63. package/src/pagination/cursor/cursor-pagination.exception.ts +0 -16
  64. package/src/pagination/cursor/cursor-pagination.helpers.ts +0 -145
  65. package/src/pagination/cursor/cursor-pagination.input.ts +0 -96
  66. package/src/pagination/cursor/cursor-pagination.types.ts +0 -127
  67. package/src/pagination/index.ts +0 -9
  68. package/src/pagination/offset/offset-pagination.exception.ts +0 -15
  69. package/src/pagination/offset/offset-pagination.helpers.ts +0 -122
  70. package/src/pagination/offset/offset-pagination.input.ts +0 -30
  71. package/src/pagination/offset/offset-pagination.types.ts +0 -82
  72. package/src/pagination/tools.ts +0 -53
  73. package/src/tools/compose.ts +0 -92
  74. package/src/tools/convert-to-bigint.ts +0 -27
  75. package/src/tools/create-list-meta.ts +0 -64
  76. package/src/tools/define-statuses.ts +0 -15
  77. package/src/tools/env.ts +0 -139
  78. package/src/tools/fetch-total-with-query.ts +0 -48
  79. package/src/tools/generate-entity-id.ts +0 -23
  80. package/src/tools/get-request-language.ts +0 -13
  81. package/src/tools/is-object.ts +0 -10
  82. package/src/tools/postgres/locale-to-pg-collate.ts +0 -21
  83. package/src/tools/remove-undefined-properties.ts +0 -20
  84. package/src/tools/request-id.ts +0 -25
  85. package/src/tools/stringify-opts.ts +0 -20
  86. package/src/tools/typeorm/add-filter.ts +0 -164
  87. package/src/tools/typeorm/ensure-inner-join.ts +0 -36
  88. package/src/tools/typeorm/ensure-left-join.ts +0 -36
  89. package/src/tools/typeorm/is-alias-already-busy.ts +0 -25
  90. package/src/tools/wait.ts +0 -26
  91. package/src/types/express-request.ts +0 -8
  92. package/src/types/list-mehod-options.ts +0 -32
  93. package/src/types/list-meta.ts +0 -16
  94. package/src/types/maybe.ts +0 -2
  95. package/src/validation/index.ts +0 -1
  96. package/src/validation/validation-pipe.ts +0 -14
  97. package/tsconfig.lib.json +0 -9
  98. package/tsdown.config.ts +0 -15
@@ -1,461 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { isObject } from '#/tools/is-object.js';
3
- import { Injectable, OnModuleInit } from '@nestjs/common';
4
- import { Interval } from '@nestjs/schedule';
5
- import { InjectDataSource } from '@nestjs/typeorm';
6
- import { EventEmitter } from 'events';
7
- import { PubSubEngine } from 'graphql-subscriptions';
8
- import {
9
- Client, ClientConfig, Notification, escapeIdentifier, escapeLiteral,
10
- } from 'pg';
11
- import {
12
- BaseEntity, DataSource, FindOptionsWhere,
13
- } from 'typeorm';
14
-
15
- import { AppConfigType, InjectAppConfig } from '#/configs/app.config';
16
- import {
17
- InjectLoggerFactory, Logger, LoggerFactory,
18
- } from '#/logger';
19
- import { MaybeNull } from '#/types/maybe';
20
-
21
- import { InjectDbConfig } from '#/configs/db.config';
22
- import { wait } from '#/tools/wait';
23
- import type { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js';
24
-
25
- export type PubSubSubscriptionCallback = (...args: unknown[]) => void;
26
-
27
- export interface PubSubSubscription {
28
- id: number;
29
- channel: string;
30
- callback: PubSubSubscriptionCallback;
31
- }
32
-
33
- /**
34
- * Entity reference
35
- * @example
36
- * {
37
- * __ref: 'Media#jsv:12jg839hkvgs'
38
- * }
39
- */
40
- export interface EntityRef {
41
- __ref: string;
42
- }
43
-
44
- export interface EntityWithId extends BaseEntity {
45
- id: string;
46
- }
47
-
48
- export interface ObjectWithId {
49
- id: string;
50
- }
51
-
52
- @Injectable()
53
- export class PostgresPubSub extends PubSubEngine implements OnModuleInit {
54
- @InjectAppConfig() declare protected readonly appConfig: AppConfigType;
55
- @InjectDbConfig() declare protected readonly dbConfig: PostgresConnectionOptions;
56
-
57
- @InjectLoggerFactory() declare private readonly loggerFactory: LoggerFactory;
58
- declare protected logger: Logger;
59
-
60
- @InjectDataSource() declare protected readonly dataSource: DataSource;
61
-
62
- protected dbClient!: Client;
63
- protected ee = new EventEmitter();
64
- protected subscriptions: PubSubSubscription[] = [];
65
-
66
- constructor(
67
- ) {
68
- super();
69
- }
70
-
71
- protected config!: ClientConfig;
72
- protected readonly retryLimit = 5;
73
- protected isReinitializing = false;
74
-
75
- async onModuleInit() {
76
- this.logger = this.loggerFactory.create({
77
- scope: this.constructor.name,
78
- });
79
-
80
- this.config = {
81
- host: this.dbConfig.host,
82
- port: this.dbConfig.port,
83
- database: this.dbConfig.database,
84
- user: this.dbConfig.username,
85
- password: this.dbConfig.password,
86
- ssl: this.dbConfig.ssl as boolean | undefined,
87
-
88
- // Pool configuration
89
- connectionTimeoutMillis: 30000, // 30 seconds to establish connection
90
-
91
- // Query timeout settings
92
- query_timeout: 30000, // 30 seconds for queries to complete
93
- statement_timeout: 30000, // 30 seconds for statements
94
-
95
- // Automatic reconnection
96
- keepAlive: true,
97
- keepAliveInitialDelayMillis: 10000, // Start keep-alive after 10 seconds
98
- };
99
-
100
- await this.reinit();
101
- }
102
-
103
- // @see fix suggestion [here](https://community.fly.io/t/postgresql-connection-issues-have-returned/6424/6)
104
- @Interval('KEEP_DB_CONNECTION_ALIVE', 5 * 1000) // each 5s
105
- async keepDbConnectionAlive() {
106
- if (this.isReinitializing) {
107
- return;
108
- }
109
-
110
- try {
111
- await this.dbClient.query('SELECT pg_backend_pid()');
112
- } catch (error) {
113
- if (error instanceof Error) {
114
- this.logger.error('PubSub PostgreSQL connection check failed:', error);
115
- }
116
-
117
- await this.reinit();
118
- }
119
- }
120
-
121
- protected async reinit() {
122
- this.isReinitializing = true;
123
-
124
- this.logger.info('🔌 Initializing PubSub PostgreSQL client...');
125
-
126
- if (this.dbClient) {
127
- this.dbClient.removeAllListeners();
128
- this.dbClient.once('error', (error) => this.logger.error(`Previous DB client errored after reconnecting already:`, error));
129
- this.dbClient.end();
130
- }
131
-
132
- this.dbClient = await this.reconnect();
133
-
134
- this.logger.info('✅ PubSub PostgreSQL client connected');
135
-
136
- this.addDbClientEventListeners();
137
-
138
- this.isReinitializing = false;
139
- }
140
-
141
- protected addDbClientEventListeners() {
142
- this.dbClient.on('notification', async (message: Notification) => {
143
- await this.processNotification(message);
144
- });
145
-
146
- this.dbClient.on('end', async () => {
147
- this.logger.warn('⛓️‍💥 PubSub PostgreSQL client ended');
148
- if (!this.isReinitializing) {
149
- await this.reinit();
150
- }
151
- });
152
-
153
- this.dbClient.on('error', async (error: Error) => {
154
- this.logger.error('🔴 PubSub PostgreSQL client error', error);
155
-
156
- if (!this.isReinitializing) {
157
- await this.reinit();
158
- }
159
- });
160
- }
161
-
162
- protected async reconnect() {
163
- this.logger.info('🔌 Connecting to PubSub PostgreSQL for notification streaming');
164
- const startTime = Date.now();
165
- const retryTimeout = 3000;
166
-
167
- for (let attempt = 1; attempt < this.retryLimit; attempt++) {
168
- this.logger.info(`🔌 PostgreSQL connection attempt #${attempt}...`);
169
-
170
- try {
171
- const dbClient = new Client(this.config);
172
- const connecting = new Promise((resolve, reject) => {
173
- dbClient.once('connect', resolve);
174
- dbClient.once('end', () => reject(Error('Connection ended.')));
175
- dbClient.once('error', reject);
176
- });
177
- await Promise.all([
178
- dbClient.connect(),
179
- connecting,
180
- ]);
181
- this.logger.info('✅ PostgreSQL connection succeeded');
182
-
183
- return dbClient;
184
- } catch (error) {
185
- if (error instanceof Error) {
186
- this.logger.error('🔌 PostgreSQL connection attempt failed:', error);
187
- }
188
- await wait(500);
189
-
190
- if (retryTimeout && (Date.now() - startTime) > retryTimeout) {
191
- throw new Error(`🚫 Stopping PostgreSQL connection attempts after ${retryTimeout}ms timeout has been reached.`);
192
- }
193
- }
194
- }
195
-
196
- throw new Error('🔴 Reconnecting notification client to PostgreSQL database failed.');
197
- }
198
-
199
- public async publish(triggerName: string, payload: any, retryCount = 1): Promise<void> {
200
- if (!payload) {
201
- throw new Error('Payload is required argument');
202
- }
203
-
204
- const channel = escapeIdentifier(triggerName);
205
-
206
- const message = escapeLiteral(JSON.stringify(this.minify(payload)));
207
-
208
- // this.logger.info('Publish PostgreSQL notification', {
209
- // channel,
210
- // message,
211
- // payload,
212
- // });
213
-
214
- try {
215
- await this.dbClient.query(`NOTIFY ${channel}, ${message}`);
216
- } catch (error) {
217
- if (error instanceof Error) {
218
- this.logger.error(`Can't publish PostgreSQL notification to channel "${channel}"`, error, {
219
- payload,
220
- });
221
-
222
- if (error.message.match(/connection/i)) {
223
- if (retryCount < 3) {
224
- this.logger.error(`Try to reconnect to PubSub PostgreSQL Client...(Retry count: ${retryCount})`);
225
- await this.dbClient.end();
226
- await this.dbClient.connect();
227
- await this.publish(triggerName, payload, retryCount + 1);
228
- }
229
- }
230
- }
231
- }
232
-
233
- return await Promise.resolve();
234
- }
235
-
236
- public async subscribe(triggerName: string, callback: PubSubSubscriptionCallback): Promise<number> {
237
- if (!this.subscriptions.some((s) => s.channel === triggerName)) {
238
- const channel = escapeIdentifier(triggerName);
239
- await this.dbClient.query(`LISTEN ${channel}`);
240
- }
241
-
242
- this.ee.on(triggerName, callback);
243
-
244
- const subId = Math.floor(Math.random() * 100000);
245
-
246
- this.subscriptions.push({
247
- id: subId,
248
- channel: triggerName,
249
- callback,
250
- });
251
-
252
- return await Promise.resolve(subId);
253
- }
254
-
255
- public async unsubscribe(subId: number) {
256
- const index = this.subscriptions.findIndex((s) => s.id === subId);
257
- if (index !== -1) {
258
- const subscription = this.subscriptions[index];
259
- this.subscriptions.splice(index, 1);
260
- this.ee.removeListener(subscription.channel, subscription.callback);
261
-
262
- if (!this.subscriptions.some((s) => s.channel === subscription.channel)) {
263
- const channel = escapeIdentifier(subscription.channel);
264
- await this.dbClient.query(`UNLISTEN ${channel}`);
265
- }
266
- }
267
- }
268
-
269
- private async processNotification(message: Notification) {
270
- try {
271
- const minifiedPayload = JSON.parse(message.payload || '');
272
-
273
- // this.logger.info('Process PostgreSQL notification', {
274
- // channel: message.channel,
275
- // minifiedObject,
276
- // });
277
-
278
- if (minifiedPayload.id) {
279
- if (Object.keys(minifiedPayload).length === 1) {
280
- this.ee.emit(message.channel, minifiedPayload);
281
- } else {
282
- this.ee.emit(message.channel, await this.unminify(minifiedPayload));
283
- }
284
- } else if (Array.isArray(minifiedPayload)) {
285
- const payload = await this.unminify(minifiedPayload);
286
-
287
- this.ee.emit(message.channel, payload);
288
- } else {
289
- const payload: any = {
290
- };
291
- for (const [key, value] of Object.entries(minifiedPayload)) {
292
- payload[key] = await this.unminify(value);
293
- }
294
-
295
- this.ee.emit(message.channel, payload);
296
- }
297
- } catch (error) {
298
- if (error instanceof Error) {
299
- this.logger.error(`Can't extract PostgreSQL notificaton from channel ${message.channel}`, error, {
300
- message,
301
- });
302
- }
303
- }
304
- }
305
-
306
- protected toRef(object: object): MaybeNull<EntityRef> {
307
- // this.logger.info('Create reference', {
308
- // object,
309
- // isEntityWithId: this.isEntityWithId(object),
310
- // });
311
-
312
- if (this.isEntityWithId(object)) {
313
- return {
314
- __ref: `${object.constructor.name}#${object.id}`,
315
- };
316
- }
317
-
318
- return null;
319
- }
320
-
321
- protected minify(payload: any): any {
322
- if (Array.isArray(payload)) {
323
- return payload.map((item) => this.minify(item));
324
- }
325
-
326
- if (!isObject(payload)) {
327
- return payload;
328
- }
329
-
330
- // this.logger.info('Minifying object', {
331
- // object,
332
- // isEntityWithId: this.isEntityWithId(object),
333
- // });
334
-
335
- if (this.isEntityWithId(payload)) {
336
- const ref = this.toRef(payload);
337
-
338
- return (ref ?? payload);
339
- }
340
-
341
- const minifiedObject: Record<string, unknown> = {
342
- };
343
-
344
- for (const key of Object.keys(payload)) {
345
- const value = payload[key];
346
-
347
- if (value instanceof Date) {
348
- minifiedObject[key] = value.toISOString();
349
- } else if (Array.isArray(value)) {
350
- minifiedObject[key] = value.map((item) => this.minify(item));
351
- } else if (isObject(value)) {
352
- minifiedObject[key] = this.minify(value);
353
- } else {
354
- minifiedObject[key] = value;
355
- }
356
- }
357
-
358
- return minifiedObject;
359
- }
360
-
361
- protected async unref(entityRef: EntityRef): Promise<BaseEntity | ObjectWithId> {
362
- const logger = this.logger.child({
363
- action: this.unref.name,
364
- entityRef,
365
- });
366
-
367
- // logger.info('Unref entity');
368
-
369
- const [entityName, id] = entityRef.__ref.split('#');
370
-
371
- if (!entityName || !id) {
372
- logger.error(`Invalid ref ${entityRef.__ref}`);
373
-
374
- return {
375
- id: entityRef.__ref,
376
- };
377
- }
378
-
379
- const entityMetadata = this.dataSource.entityMetadatas.find(
380
- (entityMetadata) => entityMetadata.name === entityName,
381
- );
382
-
383
- if (!entityMetadata?.target) {
384
- logger.error(`Entity ${entityName} not found in registry`);
385
-
386
- return {
387
- id,
388
- };
389
- }
390
-
391
- const instance = await this.dataSource.getRepository(entityMetadata.target).findOneBy({
392
- id,
393
- } as FindOptionsWhere<EntityWithId>);
394
-
395
- if (!instance) {
396
- logger.error(`Entity ${entityName} with id ${id} not found in database`);
397
-
398
- return {
399
- id,
400
- };
401
- }
402
-
403
- // logger.info('Unref entity success', {
404
- // instance,
405
- // });
406
-
407
- return instance as BaseEntity;
408
- }
409
-
410
- protected async unminify(payload: any): Promise<any> {
411
- // this.logger.info('Unminifying object', {
412
- // object,
413
- // isEntityRef: this.isEntityRef(object),
414
- // });
415
-
416
- if (Array.isArray(payload)) {
417
- return await Promise.all(payload.map((item) => this.unminify(item)));
418
- }
419
-
420
- if (this.isEntityRef(payload)) {
421
- return await this.unref(payload);
422
- }
423
-
424
- if (!isObject(payload)) {
425
- return payload;
426
- }
427
-
428
- const unminifiedObject: Record<string, unknown> = {
429
- };
430
-
431
- for (const key of Object.keys(payload)) {
432
- const value = payload[key];
433
-
434
- if (typeof value === 'string' && !isNaN(Date.parse(value))) {
435
- unminifiedObject[key] = new Date(value);
436
- } else if (this.isEntityRef(value)) {
437
- unminifiedObject[key] = await this.unref(value);
438
- } else if (Array.isArray(value)) {
439
- unminifiedObject[key] = await Promise.all(value.map((item) => this.unminify(item)));
440
- } else if (isObject(value)) {
441
- unminifiedObject[key] = await this.unminify(value);
442
- } else {
443
- unminifiedObject[key] = value;
444
- }
445
- }
446
-
447
- return unminifiedObject;
448
- }
449
-
450
- protected isEntityRef(value: unknown): value is EntityRef {
451
- return typeof value === 'object' && value !== null && '__ref' in value;
452
- }
453
-
454
- protected isEntityWithId(value: unknown): value is EntityWithId {
455
- return value instanceof BaseEntity && 'id' in value;
456
- }
457
-
458
- public asyncIterator<T>(triggers: string | readonly string[]) {
459
- return super.asyncIterableIterator(triggers) as AsyncIterator<T>;
460
- }
461
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * Pagination max limit value
3
- */
4
- export const PAGINATION_MAX_LIMIT = 1000;
5
-
6
- /**
7
- * Pagination default limit value
8
- */
9
- export const PAGINATION_DEFAULT_LIMIT = 20;
@@ -1,16 +0,0 @@
1
- import { NestError } from '#/errors';
2
-
3
- export enum CursorErrorMessage {
4
- CURSOR = 'Invalid cursor',
5
- FIRST_OR_LAST = `Should pass exactly one of arguments ('first'/'last')`,
6
- }
7
-
8
- export class InvalidCursorError extends NestError {
9
- constructor(public message: CursorErrorMessage) {
10
- super({
11
- message,
12
- key: 'NST_CURSOR_INVALID',
13
- });
14
- this.name = 'InvalidCursorError';
15
- }
16
- }
@@ -1,145 +0,0 @@
1
- import { truncPaginationLimit } from '../tools.js';
2
- import { CursorErrorMessage, InvalidCursorError } from './cursor-pagination.exception.js';
3
- import { CursorPaginationInput } from './cursor-pagination.input.js';
4
- import {
5
- CursorOrderBy, CursorPaginationPageInfo, ICursorPaginated,
6
- } from './cursor-pagination.types.js';
7
-
8
- /**
9
- * Encode cursor string to base64.
10
- * @param str - Cursor string.
11
- * @example
12
- * const cursor = '2020-01-01T00:00:00.000Z';
13
- * // encoded = 'MjAyMC0wMS0wMVQwMDowMDowMC4wMDBa';
14
- */
15
- const encodeCursor = (str: string): string => {
16
- return Buffer
17
- .from(str)
18
- .toString('base64');
19
- };
20
-
21
- /**
22
- * Decode cursor string from base64.
23
- * @param encoded - Encoded cursor string (base64).
24
- * @example
25
- * const encoded = 'MjAyMC0wMS0wMVQwMDowMDowMC4wMDBa';
26
- * // cursor = '2020-01-01T00:00:00.000Z';
27
- */
28
- const decodeCursor = (encoded: string): string => {
29
- return Buffer
30
- .from(encoded, 'base64')
31
- .toString('binary');
32
- };
33
-
34
- export interface CursorPaginationFilter {
35
- /**
36
- * Return only items created after this date.
37
- **/
38
- createdAfter?: Date;
39
-
40
- /**
41
- * Return only items created before this date.
42
- * */
43
- createdBefore?: Date;
44
- }
45
-
46
- export interface CursorPaginationOptions {
47
- /**
48
- * Limit of items to return.
49
- */
50
- limit?: number;
51
-
52
- /**
53
- * Filter options.
54
- */
55
- filter: CursorPaginationFilter;
56
-
57
- /**
58
- * Order by options.
59
- */
60
- orderBy: CursorOrderBy;
61
-
62
- /**
63
- * If true, the total count of items will be returned.
64
- */
65
- needCountTotal: false;
66
- }
67
-
68
- export const createCursorPaginationOptions = (meta: CursorPaginationInput): CursorPaginationOptions => {
69
- const {
70
- first,
71
- last,
72
- before,
73
- after,
74
- } = meta;
75
-
76
- if ((!first && !last) || (first && last)) {
77
- throw new InvalidCursorError(CursorErrorMessage.FIRST_OR_LAST);
78
- }
79
-
80
- const options: CursorPaginationOptions = {
81
- filter: {
82
- },
83
- needCountTotal: false,
84
-
85
- // If first is provided, we need to sort in ascending order, otherwise descending.
86
- orderBy: first ? CursorOrderBy.createdAt_ASC : CursorOrderBy.createdAt_DESC,
87
- };
88
-
89
- /**
90
- * If first or last is provided, we need to add 1 to the limit to determine if there is a next or previous page.
91
- */
92
- options.limit = truncPaginationLimit(first ?? last as number) + 1;
93
-
94
- if (after) {
95
- options.filter.createdAfter = new Date(decodeCursor(after));
96
- }
97
-
98
- if (before) {
99
- options.filter.createdBefore = new Date(decodeCursor(before));
100
- }
101
-
102
- return options;
103
- };
104
-
105
- export const cursorPaginationOutput = <T extends { createdAt: Date }>(nodes: T[], meta: CursorPaginationInput): ICursorPaginated<T> => {
106
- const {
107
- first,
108
- last,
109
- after,
110
- before,
111
- } = meta;
112
- const pageInfo: CursorPaginationPageInfo = new CursorPaginationPageInfo();
113
- const limit = truncPaginationLimit(first ?? last as number);
114
- const hasOneMorePage: boolean = nodes.length > limit;
115
-
116
- pageInfo.hasPreviousPage = (last && hasOneMorePage) || !!after;
117
- pageInfo.hasNextPage = (first && hasOneMorePage) || !!before;
118
-
119
- if (hasOneMorePage) {
120
- nodes.pop();
121
- }
122
-
123
- if (last) {
124
- nodes.reverse();
125
- }
126
-
127
- const edges = nodes.map((value) => {
128
- const createdAtStr = value.createdAt instanceof Date
129
- ? value.createdAt.toISOString()
130
- : value.createdAt;
131
-
132
- return {
133
- node: value,
134
- cursor: encodeCursor(createdAtStr),
135
- };
136
- });
137
-
138
- pageInfo.startCursor = edges.length > 0 ? edges[0].cursor : null;
139
- pageInfo.endCursor = edges.length > 0 ? edges.at(-1)?.cursor ?? null : null;
140
-
141
- return {
142
- edges,
143
- pageInfo,
144
- };
145
- };