@mereb/shared-packages 0.0.5
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.
- package/README.md +44 -0
- package/dist/auth/jwks.d.ts +12 -0
- package/dist/auth/jwks.d.ts.map +1 -0
- package/dist/auth/jwks.js +31 -0
- package/dist/cache/redis.d.ts +7 -0
- package/dist/cache/redis.d.ts.map +1 -0
- package/dist/cache/redis.js +27 -0
- package/dist/config/env.d.ts +10 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +56 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +16 -0
- package/dist/media/s3.d.ts +3 -0
- package/dist/media/s3.d.ts.map +1 -0
- package/dist/media/s3.js +12 -0
- package/dist/messaging/kafka.d.ts +6 -0
- package/dist/messaging/kafka.d.ts.map +1 -0
- package/dist/messaging/kafka.js +34 -0
- package/dist/observability/otel.d.ts +8 -0
- package/dist/observability/otel.d.ts.map +1 -0
- package/dist/observability/otel.js +29 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Shared Mereb Backend Packages
|
|
2
|
+
|
|
3
|
+
This package exposes the shared Fastify/OpenTelemetry helpers used across the backend services. It is built independently with TypeScript and published as `@services/shared`.
|
|
4
|
+
|
|
5
|
+
## Local development
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install
|
|
9
|
+
pnpm --filter @services/shared lint
|
|
10
|
+
pnpm --filter @services/shared typecheck
|
|
11
|
+
pnpm --filter @services/shared build
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The compiled JavaScript and type definitions land in `dist/`.
|
|
15
|
+
|
|
16
|
+
## Jenkins publishing workflow
|
|
17
|
+
|
|
18
|
+
The folder contains its own `Jenkinsfile` plus `.ci/ci.yml` so it can run through the `mereb-jenkins` shared library just like the services do.
|
|
19
|
+
|
|
20
|
+
1. **Version bump** – run `pnpm --filter @services/shared version:bump` (optionally pass `major` or `minor`). The script inspects the latest `v*` tag and updates `package.json` to the next version so the release tag and package stay in sync.
|
|
21
|
+
2. **Push to `main`** – Jenkins runs the branch build. After it lints/builds, the `release.autoTag` stage (configured with `allowDirty: true` because the build leaves `node_modules/` around) creates and pushes the next `v<semver>` tag.
|
|
22
|
+
3. **Release stages** – in the same build, the new `releaseStages` block uses that tag (`RELEASE_TAG`) to run `pnpm publish` and then the `release.github` stage publishes release notes.
|
|
23
|
+
4. **Jenkins job** – create a Multibranch Pipeline pointing at this repository. Tags are optional now, but still enable tag discovery if you want Jenkins to react to manual tags.
|
|
24
|
+
5. **Credentials** – add an `npm-registry-token` secret text credential in Jenkins for npm publish, plus `github-credentials` for tagging/GitHub releases. The pipeline writes the npm token to `.npmrc` during the publish stage.
|
|
25
|
+
6. **Registry override (optional)** – set the job environment variable `NPM_REGISTRY` if you publish somewhere other than `https://registry.npmjs.org`.
|
|
26
|
+
|
|
27
|
+
Every successful `main` build with a version bump now produces a tag, publishes the package, and creates GitHub release notes without waiting for a second job.
|
|
28
|
+
|
|
29
|
+
> Tip: The bump script accepts `major`, `minor`, or `patch` (default) so you can control which segment changes: `pnpm --filter @services/shared version:bump minor`.
|
|
30
|
+
|
|
31
|
+
> The repo installs a Husky `pre-commit` hook that automatically runs `pnpm run version:bump` and stages `services/shared/package.json`, keeping the version aligned with the next release tag before every commit. Jenkins sets `HUSKY=0` (see `.ci/ci.yml`) so the hook never runs in CI; locally you can bypass it with `HUSKY=0 git commit ...`.
|
|
32
|
+
|
|
33
|
+
## Manual publish (fallback)
|
|
34
|
+
|
|
35
|
+
If you need to publish outside Jenkins:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cd services/shared
|
|
39
|
+
pnpm install
|
|
40
|
+
pnpm lint && pnpm typecheck && pnpm build
|
|
41
|
+
NPM_TOKEN=... pnpm publish --registry https://registry.npmjs.org --no-git-checks
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Ensure the git tag matches the package version before publishing so the CI job can take over again on the next release.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type JWTPayload } from 'jose';
|
|
2
|
+
import type { IncomingHttpHeaders } from 'node:http';
|
|
3
|
+
export interface VerifyJwtOptions {
|
|
4
|
+
issuer: string;
|
|
5
|
+
audience?: string | string[];
|
|
6
|
+
cacheTtlMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function initJwks(issuer: string): Promise<void>;
|
|
9
|
+
export declare function verifyJwt(token: string, { issuer, audience }: VerifyJwtOptions): Promise<JWTPayload>;
|
|
10
|
+
export declare function extractUserId(payload: JWTPayload): string | undefined;
|
|
11
|
+
export declare function parseAuthHeader(headers: IncomingHttpHeaders): string | undefined;
|
|
12
|
+
//# sourceMappingURL=jwks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwks.d.ts","sourceRoot":"","sources":["../../src/auth/jwks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAIrD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,iBAI5C;AAED,wBAAsB,SAAS,CAC7B,KAAK,EAAE,MAAM,EACb,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,gBAAgB,uBAYvC;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,sBAEhD;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,mBAAmB,GAC3B,MAAM,GAAG,SAAS,CAMpB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initJwks = initJwks;
|
|
4
|
+
exports.verifyJwt = verifyJwt;
|
|
5
|
+
exports.extractUserId = extractUserId;
|
|
6
|
+
exports.parseAuthHeader = parseAuthHeader;
|
|
7
|
+
const jose_1 = require("jose");
|
|
8
|
+
let jwks;
|
|
9
|
+
async function initJwks(issuer) {
|
|
10
|
+
jwks = (0, jose_1.createRemoteJWKSet)(new URL(`${issuer.replace(/\/$/, '')}/protocol/openid-connect/certs`));
|
|
11
|
+
}
|
|
12
|
+
async function verifyJwt(token, { issuer, audience }) {
|
|
13
|
+
if (!jwks) {
|
|
14
|
+
await initJwks(issuer);
|
|
15
|
+
}
|
|
16
|
+
const { payload } = await (0, jose_1.jwtVerify)(token, jwks, {
|
|
17
|
+
issuer,
|
|
18
|
+
audience
|
|
19
|
+
});
|
|
20
|
+
return payload;
|
|
21
|
+
}
|
|
22
|
+
function extractUserId(payload) {
|
|
23
|
+
return typeof payload.sub === 'string' ? payload.sub : undefined;
|
|
24
|
+
}
|
|
25
|
+
function parseAuthHeader(headers) {
|
|
26
|
+
const auth = headers.authorization;
|
|
27
|
+
if (!auth || !auth.startsWith('Bearer ')) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return auth.slice('Bearer '.length);
|
|
31
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type RedisClientType } from '@redis/client';
|
|
2
|
+
export interface RedisOptions {
|
|
3
|
+
url: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function getRedisClient(options: RedisOptions): Promise<RedisClientType>;
|
|
6
|
+
export declare function disconnectRedis(): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=redis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/cache/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAOnE,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC,CASpF;AAED,wBAAsB,eAAe,kBAKpC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getRedisClient = getRedisClient;
|
|
7
|
+
exports.disconnectRedis = disconnectRedis;
|
|
8
|
+
const client_1 = require("@redis/client");
|
|
9
|
+
const pino_1 = __importDefault(require("pino"));
|
|
10
|
+
const logger = (0, pino_1.default)({ name: 'redis' });
|
|
11
|
+
let client;
|
|
12
|
+
async function getRedisClient(options) {
|
|
13
|
+
if (!client) {
|
|
14
|
+
client = (0, client_1.createClient)({ url: options.url });
|
|
15
|
+
client.on('error', (err) => {
|
|
16
|
+
logger.error({ err }, 'Redis connection error');
|
|
17
|
+
});
|
|
18
|
+
await client.connect();
|
|
19
|
+
}
|
|
20
|
+
return client;
|
|
21
|
+
}
|
|
22
|
+
async function disconnectRedis() {
|
|
23
|
+
if (client) {
|
|
24
|
+
await client.disconnect();
|
|
25
|
+
client = undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface EnvConfigOptions<T extends string> {
|
|
2
|
+
defaults?: Partial<Record<T, string>>;
|
|
3
|
+
required?: T[];
|
|
4
|
+
}
|
|
5
|
+
export declare function loadEnv(path?: string): void;
|
|
6
|
+
export declare function getEnv<T extends string>(key: T, fallback?: string): string;
|
|
7
|
+
export declare function mustGetEnv<T extends string>(keys: T[]): Record<T, string>;
|
|
8
|
+
export declare function getBooleanEnv(key: string, fallback?: boolean): boolean;
|
|
9
|
+
export declare function getNumberEnv(key: string, fallback?: number): number;
|
|
10
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,MAAM;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IACtC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;CAChB;AAED,wBAAgB,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,QAKpC;AAED,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAM1E;AAED,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CASzE;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,OAAO,CAMpE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAanE"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadEnv = loadEnv;
|
|
7
|
+
exports.getEnv = getEnv;
|
|
8
|
+
exports.mustGetEnv = mustGetEnv;
|
|
9
|
+
exports.getBooleanEnv = getBooleanEnv;
|
|
10
|
+
exports.getNumberEnv = getNumberEnv;
|
|
11
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
12
|
+
let loaded = false;
|
|
13
|
+
function loadEnv(path) {
|
|
14
|
+
if (!loaded) {
|
|
15
|
+
dotenv_1.default.config(path ? { path } : undefined);
|
|
16
|
+
loaded = true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function getEnv(key, fallback) {
|
|
20
|
+
const value = process.env[key] ?? fallback;
|
|
21
|
+
if (value === undefined) {
|
|
22
|
+
throw new Error(`Missing required environment variable ${key}`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function mustGetEnv(keys) {
|
|
27
|
+
return keys.reduce((acc, key) => {
|
|
28
|
+
const value = process.env[key];
|
|
29
|
+
if (value === undefined) {
|
|
30
|
+
throw new Error(`Missing required environment variable ${key}`);
|
|
31
|
+
}
|
|
32
|
+
acc[key] = value;
|
|
33
|
+
return acc;
|
|
34
|
+
}, {});
|
|
35
|
+
}
|
|
36
|
+
function getBooleanEnv(key, fallback = false) {
|
|
37
|
+
const value = process.env[key];
|
|
38
|
+
if (value === undefined) {
|
|
39
|
+
return fallback;
|
|
40
|
+
}
|
|
41
|
+
return ['true', '1', 'yes', 'on'].includes(value.toLowerCase());
|
|
42
|
+
}
|
|
43
|
+
function getNumberEnv(key, fallback) {
|
|
44
|
+
const value = process.env[key];
|
|
45
|
+
if (value === undefined) {
|
|
46
|
+
if (fallback === undefined) {
|
|
47
|
+
throw new Error(`Missing required numeric environment variable ${key}`);
|
|
48
|
+
}
|
|
49
|
+
return fallback;
|
|
50
|
+
}
|
|
51
|
+
const numeric = Number(value);
|
|
52
|
+
if (Number.isNaN(numeric)) {
|
|
53
|
+
throw new Error(`Environment variable ${key} must be a number`);
|
|
54
|
+
}
|
|
55
|
+
return numeric;
|
|
56
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './auth/jwks';
|
|
2
|
+
export * from './cache/redis';
|
|
3
|
+
export * from './config/env';
|
|
4
|
+
export * from './logger';
|
|
5
|
+
export * from './messaging/kafka';
|
|
6
|
+
export * from './observability/otel';
|
|
7
|
+
export * from './media/s3';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./auth/jwks"), exports);
|
|
18
|
+
__exportStar(require("./cache/redis"), exports);
|
|
19
|
+
__exportStar(require("./config/env"), exports);
|
|
20
|
+
__exportStar(require("./logger"), exports);
|
|
21
|
+
__exportStar(require("./messaging/kafka"), exports);
|
|
22
|
+
__exportStar(require("./observability/otel"), exports);
|
|
23
|
+
__exportStar(require("./media/s3"), exports);
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,+BASxC"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createLogger = createLogger;
|
|
7
|
+
const pino_1 = __importDefault(require("pino"));
|
|
8
|
+
function createLogger(name) {
|
|
9
|
+
return (0, pino_1.default)({
|
|
10
|
+
name,
|
|
11
|
+
level: process.env.LOG_LEVEL ?? 'info',
|
|
12
|
+
transport: process.env.NODE_ENV === 'production'
|
|
13
|
+
? undefined
|
|
14
|
+
: { target: 'pino-pretty', options: { translateTime: 'SYS:standard' } }
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../src/media/s3.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGvE"}
|
package/dist/media/s3.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.signMediaUrl = signMediaUrl;
|
|
4
|
+
exports.signUploadKey = signUploadKey;
|
|
5
|
+
function signMediaUrl(key) {
|
|
6
|
+
const base = process.env.MEDIA_CDN_ORIGIN ?? 'https://cdn.example.com';
|
|
7
|
+
return `${base.replace(/\/$/, '')}/${key}`;
|
|
8
|
+
}
|
|
9
|
+
function signUploadKey(ownerId, filename) {
|
|
10
|
+
const timestamp = Date.now();
|
|
11
|
+
return `users/${ownerId}/${timestamp}-${filename}`;
|
|
12
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Kafka, type KafkaConfig, type Producer, type Consumer } from 'kafkajs';
|
|
2
|
+
export declare function getKafka(config: KafkaConfig): Kafka;
|
|
3
|
+
export declare function getProducer(config: KafkaConfig): Promise<Producer>;
|
|
4
|
+
export declare function createConsumer(config: KafkaConfig, groupId: string): Promise<Consumer>;
|
|
5
|
+
export declare function disconnectProducer(): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=kafka.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kafka.d.ts","sourceRoot":"","sources":["../../src/messaging/kafka.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAKhF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,WAAW,SAK3C;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAMxE;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAK5F;AAED,wBAAsB,kBAAkB,kBAKvC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getKafka = getKafka;
|
|
4
|
+
exports.getProducer = getProducer;
|
|
5
|
+
exports.createConsumer = createConsumer;
|
|
6
|
+
exports.disconnectProducer = disconnectProducer;
|
|
7
|
+
const kafkajs_1 = require("kafkajs");
|
|
8
|
+
let kafkaInstance;
|
|
9
|
+
let producerInstance;
|
|
10
|
+
function getKafka(config) {
|
|
11
|
+
if (!kafkaInstance) {
|
|
12
|
+
kafkaInstance = new kafkajs_1.Kafka(config);
|
|
13
|
+
}
|
|
14
|
+
return kafkaInstance;
|
|
15
|
+
}
|
|
16
|
+
async function getProducer(config) {
|
|
17
|
+
if (!producerInstance) {
|
|
18
|
+
producerInstance = getKafka(config).producer();
|
|
19
|
+
await producerInstance.connect();
|
|
20
|
+
}
|
|
21
|
+
return producerInstance;
|
|
22
|
+
}
|
|
23
|
+
async function createConsumer(config, groupId) {
|
|
24
|
+
const kafka = getKafka(config);
|
|
25
|
+
const consumer = kafka.consumer({ groupId });
|
|
26
|
+
await consumer.connect();
|
|
27
|
+
return consumer;
|
|
28
|
+
}
|
|
29
|
+
async function disconnectProducer() {
|
|
30
|
+
if (producerInstance) {
|
|
31
|
+
await producerInstance.disconnect();
|
|
32
|
+
producerInstance = undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Instrumentation } from '@opentelemetry/instrumentation';
|
|
2
|
+
export interface OtelConfig {
|
|
3
|
+
serviceName: string;
|
|
4
|
+
otlpEndpoint?: string;
|
|
5
|
+
instrumentations?: Instrumentation[];
|
|
6
|
+
}
|
|
7
|
+
export declare function initTelemetry({ serviceName, otlpEndpoint, instrumentations }: OtelConfig): void;
|
|
8
|
+
//# sourceMappingURL=otel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel.d.ts","sourceRoot":"","sources":["../../src/observability/otel.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAOtE,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;CACtC;AAED,wBAAgB,aAAa,CAAC,EAC5B,WAAW,EACX,YAAY,EACZ,gBAAqB,EACtB,EAAE,UAAU,QA0BZ"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initTelemetry = initTelemetry;
|
|
4
|
+
const api_1 = require("@opentelemetry/api");
|
|
5
|
+
const resources_1 = require("@opentelemetry/resources");
|
|
6
|
+
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
|
|
7
|
+
const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
|
|
8
|
+
const sdk_trace_node_1 = require("@opentelemetry/sdk-trace-node");
|
|
9
|
+
const exporter_trace_otlp_grpc_1 = require("@opentelemetry/exporter-trace-otlp-grpc");
|
|
10
|
+
const instrumentation_1 = require("@opentelemetry/instrumentation");
|
|
11
|
+
api_1.diag.setLogger(new api_1.DiagConsoleLogger(), api_1.DiagLogLevel.INFO);
|
|
12
|
+
let initialized = false;
|
|
13
|
+
function initTelemetry({ serviceName, otlpEndpoint, instrumentations = [] }) {
|
|
14
|
+
if (initialized) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const resource = new resources_1.Resource({
|
|
18
|
+
[semantic_conventions_1.SemanticResourceAttributes.SERVICE_NAME]: serviceName
|
|
19
|
+
});
|
|
20
|
+
const provider = new sdk_trace_node_1.NodeTracerProvider({ resource });
|
|
21
|
+
if (otlpEndpoint) {
|
|
22
|
+
provider.addSpanProcessor(new sdk_trace_base_1.BatchSpanProcessor(new exporter_trace_otlp_grpc_1.OTLPTraceExporter({ url: otlpEndpoint })));
|
|
23
|
+
}
|
|
24
|
+
provider.register();
|
|
25
|
+
if (instrumentations.length) {
|
|
26
|
+
(0, instrumentation_1.registerInstrumentations)({ instrumentations });
|
|
27
|
+
}
|
|
28
|
+
initialized = true;
|
|
29
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mereb/shared-packages",
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@fastify/cors": "^9.0.1",
|
|
17
|
+
"@fastify/rate-limit": "^10.2.0",
|
|
18
|
+
"@fastify/sensible": "^5.6.0",
|
|
19
|
+
"@fastify/under-pressure": "^9.0.3",
|
|
20
|
+
"@opentelemetry/api": "^1.8.0",
|
|
21
|
+
"@opentelemetry/exporter-trace-otlp-grpc": "^0.52.0",
|
|
22
|
+
"@opentelemetry/host-metrics": "^0.36.2",
|
|
23
|
+
"@opentelemetry/instrumentation": "^0.52.0",
|
|
24
|
+
"@opentelemetry/instrumentation-fastify": "^0.52.0",
|
|
25
|
+
"@opentelemetry/instrumentation-http": "^0.52.0",
|
|
26
|
+
"@opentelemetry/resources": "^1.8.0",
|
|
27
|
+
"@opentelemetry/sdk-metrics": "^1.18.0",
|
|
28
|
+
"@opentelemetry/sdk-trace-base": "^1.18.0",
|
|
29
|
+
"@opentelemetry/sdk-trace-node": "^1.18.0",
|
|
30
|
+
"@opentelemetry/semantic-conventions": "^1.23.0",
|
|
31
|
+
"@redis/client": "^1.5.12",
|
|
32
|
+
"dotenv": "^16.4.5",
|
|
33
|
+
"jose": "^5.2.3",
|
|
34
|
+
"kafkajs": "^2.2.4",
|
|
35
|
+
"pino": "^9.2.0",
|
|
36
|
+
"pino-pretty": "^10.3.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"husky": "^9.1.7",
|
|
40
|
+
"@types/node": "^20.12.7",
|
|
41
|
+
"eslint": "^8.57.0",
|
|
42
|
+
"rimraf": "^5.0.5",
|
|
43
|
+
"typescript": "^5.4.5"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc -b",
|
|
47
|
+
"clean": "rimraf dist",
|
|
48
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"version:bump": "node ./scripts/bump-version.mjs"
|
|
51
|
+
}
|
|
52
|
+
}
|