Add cache-proxy application with initial setup and configuration
- Created a new cache-proxy application using NestJS, including essential files such as Dockerfile, .gitignore, and .eslintrc.js. - Implemented core application structure with AppModule, ProxyModule, and ProxyController for handling GraphQL requests. - Configured caching with Redis and established environment variable management using Zod for validation. - Added utility functions for query handling and time management, enhancing the application's functionality. - Included README.md for project documentation and setup instructions.
This commit is contained in:
parent
9d9ba6540b
commit
b8b8ca6004
7
apps/cache-proxy/.dockerignore
Normal file
7
apps/cache-proxy/.dockerignore
Normal file
@ -0,0 +1,7 @@
|
||||
.git
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
*.log
|
||||
dist
|
||||
README.md
|
||||
13
apps/cache-proxy/.eslintrc.js
Normal file
13
apps/cache-proxy/.eslintrc.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { typescript } from '@repo/eslint-config/typescript';
|
||||
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
export default [
|
||||
...typescript,
|
||||
{
|
||||
ignores: ['**/types/**', '*.config.*', '*.config.js', '.eslintrc.js'],
|
||||
rules: {
|
||||
'import/no-duplicates': 'off',
|
||||
'import/consistent-type-specifier-style': 'off',
|
||||
},
|
||||
},
|
||||
];
|
||||
56
apps/cache-proxy/.gitignore
vendored
Normal file
56
apps/cache-proxy/.gitignore
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
/build
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pnpm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/.nyc_output
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# temp directory
|
||||
.temp
|
||||
.tmp
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
45
apps/cache-proxy/Dockerfile
Normal file
45
apps/cache-proxy/Dockerfile
Normal file
@ -0,0 +1,45 @@
|
||||
# This Dockerfile is copy-pasted into our main docs at /docs/handbook/deploying-with-docker.
|
||||
# Make sure you update both files!
|
||||
|
||||
FROM node:alpine AS builder
|
||||
RUN corepack enable && corepack prepare pnpm@8.9.0 --activate
|
||||
ENV PNPM_HOME=/usr/local/bin
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat
|
||||
RUN apk update
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
RUN pnpm add -g turbo@1.12.4 dotenv-cli
|
||||
COPY . .
|
||||
RUN turbo prune --scope=api --docker
|
||||
|
||||
# Add lockfile and package.json's of isolated subworkspace
|
||||
FROM node:alpine AS installer
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
ENV PNPM_HOME=/usr/local/bin
|
||||
RUN apk add --no-cache libc6-compat
|
||||
RUN apk update
|
||||
WORKDIR /app
|
||||
|
||||
# First install dependencies (as they change less often)
|
||||
COPY .gitignore .gitignore
|
||||
COPY --from=builder /app/out/json/ .
|
||||
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
COPY --from=builder /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||
RUN pnpm install
|
||||
|
||||
# Build the project and its dependencies
|
||||
COPY --from=builder /app/out/full/ .
|
||||
COPY turbo.json turbo.json
|
||||
RUN pnpm dotenv -e .env turbo run build --filter=api...
|
||||
|
||||
FROM node:alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
# Don't run production as root
|
||||
RUN addgroup --system --gid 1001 nestjs
|
||||
RUN adduser --system --uid 1001 nestjs
|
||||
USER nestjs
|
||||
COPY --from=installer /app .
|
||||
|
||||
CMD node apps/api/dist/main.js
|
||||
73
apps/cache-proxy/README.md
Normal file
73
apps/cache-proxy/README.md
Normal file
@ -0,0 +1,73 @@
|
||||
<p align="center">
|
||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
|
||||
</p>
|
||||
|
||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||
</p>
|
||||
<!--[](https://opencollective.com/nest#backer)
|
||||
[](https://opencollective.com/nest#sponsor)-->
|
||||
|
||||
## Description
|
||||
|
||||
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ pnpm install
|
||||
```
|
||||
|
||||
## Running the app
|
||||
|
||||
```bash
|
||||
# development
|
||||
$ pnpm run start
|
||||
|
||||
# watch mode
|
||||
$ pnpm run start:dev
|
||||
|
||||
# production mode
|
||||
$ pnpm run start:prod
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
# unit tests
|
||||
$ pnpm run test
|
||||
|
||||
# e2e tests
|
||||
$ pnpm run test:e2e
|
||||
|
||||
# test coverage
|
||||
$ pnpm run test:cov
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||
|
||||
## Stay in touch
|
||||
|
||||
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
|
||||
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||
|
||||
## License
|
||||
|
||||
Nest is [MIT licensed](LICENSE).
|
||||
9
apps/cache-proxy/nest-cli.json
Normal file
9
apps/cache-proxy/nest-cli.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"monorepo": true,
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
||||
74
apps/cache-proxy/package.json
Normal file
74
apps/cache-proxy/package.json
Normal file
@ -0,0 +1,74 @@
|
||||
{
|
||||
"name": "cache-proxy",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"dev": "dotenv -e ../../.env.local nest start -- --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/cache-manager": "^2.2.1",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/config": "^3.2.0",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
"@nestjs/platform-express": "^10.0.0",
|
||||
"@nestjs/platform-fastify": "^10.3.3",
|
||||
"cache-manager": "^5.4.0",
|
||||
"cache-manager-ioredis": "^2.1.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"reflect-metadata": "^0.2.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
"@nestjs/schematics": "^10.0.0",
|
||||
"@nestjs/testing": "^10.0.0",
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/node": "catalog:",
|
||||
"@types/supertest": "^6.0.0",
|
||||
"dotenv-cli": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"fastify": "^4.26.1",
|
||||
"jest": "^29.5.0",
|
||||
"prettier": "catalog:",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "29.1.1",
|
||||
"ts-loader": "^9.4.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
16
apps/cache-proxy/src/app.module.ts
Normal file
16
apps/cache-proxy/src/app.module.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { ProxyModule } from './proxy/proxy.module';
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
}),
|
||||
ProxyModule,
|
||||
],
|
||||
providers: [],
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||
export class AppModule {}
|
||||
3
apps/cache-proxy/src/config/constants.ts
Normal file
3
apps/cache-proxy/src/config/constants.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { seconds } from 'src/utils/time';
|
||||
|
||||
export const DEFAULT_CACHE_TTL = seconds().fromMinutes(5);
|
||||
3
apps/cache-proxy/src/config/env.ts
Normal file
3
apps/cache-proxy/src/config/env.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import envSchema from './schema/env';
|
||||
|
||||
export const env = envSchema.parse(process.env);
|
||||
22
apps/cache-proxy/src/config/schema/env.ts
Normal file
22
apps/cache-proxy/src/config/schema/env.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { DEFAULT_CACHE_TTL } from '../constants';
|
||||
import { z } from 'zod';
|
||||
|
||||
const envSchema = z.object({
|
||||
CACHE_TTL: z
|
||||
.string()
|
||||
.transform((val) => Number.parseInt(val, 10))
|
||||
.default(DEFAULT_CACHE_TTL.toString()),
|
||||
PORT: z
|
||||
.string()
|
||||
.transform((val) => Number.parseInt(val, 10))
|
||||
.default('5000'),
|
||||
REDIS_HOST: z.string(),
|
||||
REDIS_PORT: z
|
||||
.string()
|
||||
.transform((val) => Number.parseInt(val, 10))
|
||||
.default('6379'),
|
||||
REDIS_PASSWORD: z.string(),
|
||||
URL_GRAPHQL: z.string(),
|
||||
});
|
||||
|
||||
export default envSchema;
|
||||
15
apps/cache-proxy/src/main.ts
Normal file
15
apps/cache-proxy/src/main.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { AppModule } from './app.module';
|
||||
import { env } from './config/env';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
|
||||
import { FastifyAdapter } from '@nestjs/platform-fastify';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create<NestFastifyApplication>(
|
||||
AppModule,
|
||||
new FastifyAdapter(),
|
||||
);
|
||||
|
||||
await app.listen(env.PORT, '0.0.0.0');
|
||||
}
|
||||
bootstrap();
|
||||
6
apps/cache-proxy/src/proxy/lib/config.ts
Normal file
6
apps/cache-proxy/src/proxy/lib/config.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { seconds } from 'src/utils/time';
|
||||
|
||||
export const queryTTL: Record<string, number | false> = {
|
||||
Login: false,
|
||||
GetSubscriptions: seconds().fromHours(12),
|
||||
};
|
||||
138
apps/cache-proxy/src/proxy/proxy.controller.ts
Normal file
138
apps/cache-proxy/src/proxy/proxy.controller.ts
Normal file
@ -0,0 +1,138 @@
|
||||
import type { GQLRequest, GQLResponse } from './types';
|
||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import {
|
||||
All,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Inject,
|
||||
Query,
|
||||
Req,
|
||||
Res,
|
||||
} from '@nestjs/common';
|
||||
import type { Cache } from 'cache-manager';
|
||||
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { env } from 'src/config/env';
|
||||
import { queryTTL } from './lib/config';
|
||||
import { extractDocumentId, getQueryType } from 'src/utils/query';
|
||||
|
||||
type RedisStore = Omit<Cache, 'set'> & {
|
||||
set: (key: string, value: unknown, { ttl }: { ttl: number }) => Promise<void>;
|
||||
};
|
||||
|
||||
@Controller('proxy')
|
||||
export class ProxyController {
|
||||
constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: RedisStore) {}
|
||||
|
||||
@All('/graphql')
|
||||
public async graphql(@Req() req: FastifyRequest, @Res() reply: FastifyReply) {
|
||||
const { operationName, query, variables } = req.body as GQLRequest;
|
||||
|
||||
const queryType = getQueryType(query);
|
||||
|
||||
const key = `${operationName} ${JSON.stringify(variables)}`;
|
||||
|
||||
if (queryType.action === 'query') {
|
||||
const cached = await this.cacheManager.get(key);
|
||||
if (cached) return reply.send(cached);
|
||||
}
|
||||
|
||||
const response = await fetch(env.URL_GRAPHQL, {
|
||||
body: JSON.stringify({ operationName, query, variables }),
|
||||
headers: {
|
||||
Authorization: req.headers.authorization,
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: req.headers.cookie,
|
||||
},
|
||||
method: req.method,
|
||||
});
|
||||
|
||||
const data = (await response.json()) as GQLResponse;
|
||||
|
||||
if (!response.ok || data?.error || data?.errors?.length)
|
||||
throw new HttpException(
|
||||
response.statusText,
|
||||
response.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
|
||||
if (queryType.action === 'mutation' && queryType.entity) {
|
||||
const documentId = extractDocumentId(data);
|
||||
const keys = await this.cacheManager.store.keys(`*${queryType.entity}*`);
|
||||
for (const key of keys) {
|
||||
if (key.includes(documentId)) {
|
||||
await this.cacheManager.del(key);
|
||||
|
||||
// console.log(`🗑 Cache invalidated (by key): ${key}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = await this.cacheManager.get(key);
|
||||
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
if (serialized?.includes(documentId)) {
|
||||
await this.cacheManager.del(key);
|
||||
|
||||
// console.log(`🗑 Cache invalidated (by value): ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ttl = queryTTL[operationName];
|
||||
if (queryType.action === 'query' && data && ttl !== false)
|
||||
await this.cacheManager.set(key, data, { ttl: ttl || env.CACHE_TTL });
|
||||
|
||||
return reply.send(data);
|
||||
}
|
||||
|
||||
@Get('/get-queries')
|
||||
public async getQueriesList(@Res() reply: FastifyReply) {
|
||||
const keys: string[] = await this.cacheManager.store.keys('*');
|
||||
|
||||
const entries = await Promise.all(
|
||||
keys.map(async (key) => {
|
||||
try {
|
||||
const value = await this.cacheManager.get(key);
|
||||
return { key, value };
|
||||
} catch (e) {
|
||||
return { key, error: e.message };
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return reply.send(entries);
|
||||
}
|
||||
|
||||
@Delete('/delete-query')
|
||||
public async deleteQuery(@Query('queryKey') queryKey: string, @Res() reply: FastifyReply) {
|
||||
try {
|
||||
await this.cacheManager.del(queryKey);
|
||||
|
||||
return reply.send('ok');
|
||||
} catch (error) {
|
||||
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Delete('/reset')
|
||||
public async reset(@Res() reply: FastifyReply) {
|
||||
try {
|
||||
await this.cacheManager.reset();
|
||||
|
||||
return reply.send('ok');
|
||||
} catch (error) {
|
||||
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/get-query')
|
||||
public async getQueryValue(@Query('queryKey') queryKey: string, @Res() reply: FastifyReply) {
|
||||
try {
|
||||
const value = await this.cacheManager.get(queryKey);
|
||||
|
||||
return reply.send(value);
|
||||
} catch (error) {
|
||||
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
apps/cache-proxy/src/proxy/proxy.module.ts
Normal file
21
apps/cache-proxy/src/proxy/proxy.module.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { ProxyController } from './proxy.controller';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import { Module } from '@nestjs/common';
|
||||
import * as redisStore from 'cache-manager-ioredis';
|
||||
import type { RedisOptions } from 'ioredis';
|
||||
import { env } from 'src/config/env';
|
||||
|
||||
@Module({
|
||||
controllers: [ProxyController],
|
||||
imports: [
|
||||
CacheModule.register<RedisOptions>({
|
||||
host: env.REDIS_HOST,
|
||||
port: env.REDIS_PORT,
|
||||
store: redisStore,
|
||||
ttl: env.CACHE_TTL,
|
||||
password: env.REDIS_PASSWORD,
|
||||
}),
|
||||
],
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||
export class ProxyModule {}
|
||||
16
apps/cache-proxy/src/proxy/types.ts
Normal file
16
apps/cache-proxy/src/proxy/types.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export type GQLRequest = {
|
||||
operationName: string;
|
||||
query: string;
|
||||
variables: string;
|
||||
};
|
||||
|
||||
export type GQLResponse = {
|
||||
data: unknown;
|
||||
error?: unknown;
|
||||
errors?: unknown[];
|
||||
};
|
||||
|
||||
export type QueryItem = {
|
||||
queries: string[];
|
||||
ttl: number | false;
|
||||
};
|
||||
22
apps/cache-proxy/src/utils/query.ts
Normal file
22
apps/cache-proxy/src/utils/query.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { GQLResponse } from 'src/proxy/types';
|
||||
|
||||
export function getQueryType(query: string) {
|
||||
const actionMatch = query.match(/\b(query|mutation)\b/u);
|
||||
const action = actionMatch ? (actionMatch[1] as 'query' | 'mutation') : null;
|
||||
|
||||
const entityMatch = query.match(
|
||||
/\b(mutation|query)\s+\w*([A-Z][A-Za-z0-9_]+)/u,
|
||||
);
|
||||
const entity = entityMatch ? entityMatch[2] : null;
|
||||
|
||||
return { action, entity };
|
||||
}
|
||||
|
||||
export function extractDocumentId(data: GQLResponse) {
|
||||
if (!data?.data) return null;
|
||||
|
||||
const firstKey = Object.keys(data.data)[0];
|
||||
if (!firstKey) return null;
|
||||
|
||||
return data.data[firstKey]?.documentId || null;
|
||||
}
|
||||
13
apps/cache-proxy/src/utils/time.ts
Normal file
13
apps/cache-proxy/src/utils/time.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export function seconds() {
|
||||
return {
|
||||
fromDays(days: number) {
|
||||
return days * 24 * 60 * 60;
|
||||
},
|
||||
fromHours(hours: number) {
|
||||
return hours * 60 * 60;
|
||||
},
|
||||
fromMinutes(minutes: number) {
|
||||
return minutes * 60;
|
||||
},
|
||||
};
|
||||
}
|
||||
4
apps/cache-proxy/tsconfig.build.json
Normal file
4
apps/cache-proxy/tsconfig.build.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
}
|
||||
22
apps/cache-proxy/tsconfig.json
Normal file
22
apps/cache-proxy/tsconfig.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@ -10,7 +10,7 @@ export function createApolloClient(parameters?: Parameters) {
|
||||
cache: new InMemoryCache(),
|
||||
link: createLink({
|
||||
token: parameters?.token,
|
||||
uri: environment.URL_GRAPHQL,
|
||||
uri: environment.URL_GRAPHQL_CACHED,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ApolloLink, from, HttpLink } from '@apollo/client';
|
||||
import { ApolloLink, from, HttpLink } from '@apollo/client/core';
|
||||
|
||||
type Parameters = { token: null | string | undefined; uri: string };
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ export const envSchema = z.object({
|
||||
LOGIN_GRAPHQL: z.string(),
|
||||
PASSWORD_GRAPHQL: z.string(),
|
||||
URL_GRAPHQL: z.string(),
|
||||
URL_GRAPHQL_CACHED: z.string().default('http://localhost:5000/proxy/graphql'),
|
||||
});
|
||||
|
||||
export const env = envSchema.parse(process.env);
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
"@repo/utils": "workspace:",
|
||||
"@types/jsonwebtoken": "^9.0.7",
|
||||
"dayjs": "catalog:",
|
||||
"ioredis": "^5.7.0",
|
||||
"jsonwebtoken": "catalog:",
|
||||
"radashi": "catalog:",
|
||||
"vite-tsconfig-paths": "catalog:",
|
||||
|
||||
4666
pnpm-lock.yaml
generated
4666
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user