@tmlmobilidade/utils 20250325.1729.16

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 ADDED
@@ -0,0 +1,13 @@
1
+ ## @tmlmobilidade/utils
2
+
3
+ This package provides a collection of common utility functions used across projects within the organization.
4
+
5
+ ### Purpose
6
+ - Centralize reusable logic
7
+ - Promote consistency and reduce code duplication
8
+ - Simplify development with well-tested, shared helpers
9
+
10
+ ### Installation
11
+ ```
12
+ npm install @tmlmobilidade/utils
13
+ ```
@@ -0,0 +1,7 @@
1
+ export * from './src/convert-object.js';
2
+ export * from './src/generate-random-string.js';
3
+ export * from './src/generate-random-token.js';
4
+ export * from './src/operational-date.js';
5
+ export * from './src/permissions.js';
6
+ export * from './src/singleton-proxy.js';
7
+ export * from './src/unix-timestamp.js';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /* * */
2
+ export * from './src/convert-object.js';
3
+ export * from './src/generate-random-string.js';
4
+ export * from './src/generate-random-token.js';
5
+ export * from './src/operational-date.js';
6
+ export * from './src/permissions.js';
7
+ export * from './src/singleton-proxy.js';
8
+ export * from './src/unix-timestamp.js';
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Convert an object to match a Zod schema's shape.
4
+ * @param source - The source object to convert
5
+ * @param template - The Zod schema to validate against
6
+ * @returns The converted object containing only the fields defined in the schema
7
+ * @throws {Error} If a required field is missing from the source object
8
+ */
9
+ export declare function convertObject<T extends z.ZodObject<z.ZodRawShape>>(source: unknown, template: T): z.infer<T>;
@@ -0,0 +1,31 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /**
3
+ * Convert an object to match a Zod schema's shape.
4
+ * @param source - The source object to convert
5
+ * @param template - The Zod schema to validate against
6
+ * @returns The converted object containing only the fields defined in the schema
7
+ * @throws {Error} If a required field is missing from the source object
8
+ */
9
+ export function convertObject(source, template) {
10
+ //
11
+ const templateKeys = Object.keys(template.shape);
12
+ const result = {};
13
+ for (const key of templateKeys) {
14
+ const fieldSchema = template.shape[key];
15
+ const value = source[key];
16
+ // Handle undefined/null values
17
+ if (value === undefined || value === null) {
18
+ if (!fieldSchema.nullish()) {
19
+ throw new Error(`Required field "${key}" is missing in the source object`);
20
+ }
21
+ continue;
22
+ }
23
+ const parsedValue = fieldSchema.safeParse(value);
24
+ if (!parsedValue.success) {
25
+ throw new Error(`Invalid field type for "${key}" in the source object:\n> ${parsedValue.error.errors.map(error => error.message).join('\n')}`);
26
+ }
27
+ // Add the value to result
28
+ result[key] = value;
29
+ }
30
+ return result;
31
+ }
@@ -0,0 +1,12 @@
1
+ interface GenerateRandomStringOptions {
2
+ length?: number;
3
+ type?: 'alphanumeric' | 'numeric';
4
+ }
5
+ /**
6
+ * Creates a random string of a given length and type.
7
+ * @param length - The length of the string to generate.
8
+ * @param type - The type of characters to include in the string.
9
+ * @returns A random string of the specified length.
10
+ */
11
+ export declare function generateRandomString({ length, type }?: GenerateRandomStringOptions): string;
12
+ export {};
@@ -0,0 +1,27 @@
1
+ /* * */
2
+ /**
3
+ * Creates a random string of a given length and type.
4
+ * @param length - The length of the string to generate.
5
+ * @param type - The type of characters to include in the string.
6
+ * @returns A random string of the specified length.
7
+ */
8
+ export function generateRandomString({ length = 6, type = 'alphanumeric' } = {}) {
9
+ //
10
+ const numericSet = '0123456789';
11
+ const alphanumericSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
12
+ let allowedCharacters;
13
+ switch (type) {
14
+ case 'numeric':
15
+ allowedCharacters = numericSet;
16
+ break;
17
+ default:
18
+ allowedCharacters = alphanumericSet;
19
+ break;
20
+ }
21
+ let result = '';
22
+ for (let i = 0; i < length; i++) {
23
+ result += allowedCharacters.charAt(Math.floor(Math.random() * allowedCharacters.length));
24
+ }
25
+ return result;
26
+ //
27
+ }
@@ -0,0 +1 @@
1
+ export declare function generateRandomToken(): string;
@@ -0,0 +1,6 @@
1
+ /* * */
2
+ import { v4 as uuid } from 'uuid';
3
+ /* * */
4
+ export function generateRandomToken() {
5
+ return Buffer.from(uuid()).toString('base64');
6
+ }
@@ -0,0 +1,10 @@
1
+ import { type OperationalDate } from '@tmlmobilidade/types';
2
+ import { DateTime } from 'luxon';
3
+ /**
4
+ * Returns the operational date based on the provided timestamp and format.
5
+ *
6
+ * @param timestamp - The timestamp to be parsed.
7
+ * @param format - The format of the timestamp.
8
+ * @returns The operational date in the yyyyLLdd format.
9
+ */
10
+ export declare function getOperationalDate(timestamp?: DateTime | string, format?: string): OperationalDate;
@@ -0,0 +1,43 @@
1
+ /* * */
2
+ import { DateTime } from 'luxon';
3
+ /**
4
+ * Returns the operational date based on the provided timestamp and format.
5
+ *
6
+ * @param timestamp - The timestamp to be parsed.
7
+ * @param format - The format of the timestamp.
8
+ * @returns The operational date in the yyyyLLdd format.
9
+ */
10
+ export function getOperationalDate(timestamp, format) {
11
+ //
12
+ //
13
+ // Detect the type of the timestamp and parse it
14
+ let dateObject;
15
+ if (typeof timestamp === 'string' && format) {
16
+ dateObject = DateTime.fromFormat(timestamp, format);
17
+ if (!dateObject.isValid)
18
+ throw new Error(`Invalid date format '${timestamp}', expected format: ${format}, explanation: ${dateObject.invalidExplanation}`);
19
+ }
20
+ else if (timestamp && timestamp.isValid) {
21
+ dateObject = timestamp;
22
+ }
23
+ else {
24
+ dateObject = DateTime.now();
25
+ }
26
+ //
27
+ // Check if the time is between 00:00 and 03:59.
28
+ // The operational date is between 04:00 and 03:59 of the following day.
29
+ let operationalDate;
30
+ if (dateObject.hour < 4) {
31
+ // If true, return the previous day in the yyyyLLdd format
32
+ const previousDay = dateObject.minus({ days: 1 });
33
+ operationalDate = previousDay.toFormat('yyyyLLdd');
34
+ }
35
+ else {
36
+ // Else, return the current day in the yyyyLLdd format
37
+ operationalDate = dateObject.toFormat('yyyyLLdd');
38
+ }
39
+ //
40
+ // Validate the operational date and return it
41
+ return operationalDate;
42
+ //
43
+ }
@@ -0,0 +1,9 @@
1
+ import { type Permission } from '@tmlmobilidade/types';
2
+ /**
3
+ * Get a permission from a list of permissions
4
+ * @param permissions - The list of permissions
5
+ * @param scope - The scope of the permission
6
+ * @param action - The action of the permission
7
+ * @returns The permission
8
+ */
9
+ export declare function getPermission(permissions: Permission<unknown>[], scope: string, action: string): Permission<unknown>;
@@ -0,0 +1,19 @@
1
+ /* * */
2
+ import { mergekit } from 'mergekit';
3
+ /**
4
+ * Get a permission from a list of permissions
5
+ * @param permissions - The list of permissions
6
+ * @param scope - The scope of the permission
7
+ * @param action - The action of the permission
8
+ * @returns The permission
9
+ */
10
+ export function getPermission(permissions, scope, action) {
11
+ return mergekit([...permissions], {
12
+ appendArrays: true,
13
+ dedupArrays: true,
14
+ onlyObjectWithKeyValues: [
15
+ { key: 'scope', value: scope },
16
+ { key: 'action', value: action },
17
+ ],
18
+ });
19
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Creates a proxy for a singleton class that delays method access until the instance is initialized.
3
+ * @param cls - A class with a static `getInstance` method that returns a promise resolving to the class instance.
4
+ * @returns A proxy object that intercepts method calls and ensures the instance is initialized before invoking them.
5
+ */
6
+ export declare function AsyncSingletonProxy<T extends object>(cls: {
7
+ getInstance: () => Promise<T>;
8
+ }): T;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Creates a proxy for a singleton class that delays method access until the instance is initialized.
3
+ * @param cls - A class with a static `getInstance` method that returns a promise resolving to the class instance.
4
+ * @returns A proxy object that intercepts method calls and ensures the instance is initialized before invoking them.
5
+ */
6
+ export function AsyncSingletonProxy(cls) {
7
+ return new Proxy({}, {
8
+ get: function (_target, prop, receiver) {
9
+ return async (...args) => {
10
+ const instance = await cls.getInstance();
11
+ const targetProp = Reflect.get(instance, prop, receiver);
12
+ if (typeof targetProp === 'function') {
13
+ return targetProp.apply(instance, args);
14
+ }
15
+ return targetProp;
16
+ };
17
+ },
18
+ });
19
+ }
@@ -0,0 +1,10 @@
1
+ import { type OperationalDate, type UnixTimestamp } from '@tmlmobilidade/types';
2
+ /**
3
+ * Returns the current Unix Timestamp, in milliseconds.
4
+ * @returns The current Unix Timestamp.
5
+ */
6
+ export declare function getUnixTimestamp(): UnixTimestamp;
7
+ export declare function getUnixTimestampFromOperationalDate(date: OperationalDate): UnixTimestamp;
8
+ export declare function getUnixTimestampFromFormat(date: string, format: string): UnixTimestamp;
9
+ export declare function getUnixTimestampFromJSDate(date: Date): UnixTimestamp;
10
+ export declare function getUnixTimestampFromSeconds(seconds: number): UnixTimestamp;
@@ -0,0 +1,34 @@
1
+ /* * */
2
+ import { OPERATIONAL_DATE_FORMAT } from '@tmlmobilidade/types';
3
+ import { DateTime } from 'luxon';
4
+ /**
5
+ * Returns the current Unix Timestamp, in milliseconds.
6
+ * @returns The current Unix Timestamp.
7
+ */
8
+ export function getUnixTimestamp() {
9
+ return DateTime.now().toMillis();
10
+ }
11
+ export function getUnixTimestampFromOperationalDate(date) {
12
+ const parsedDate = DateTime.fromFormat(date, OPERATIONAL_DATE_FORMAT);
13
+ if (!parsedDate.isValid)
14
+ throw new Error(`Invalid date format '${date}', expected format: ${OPERATIONAL_DATE_FORMAT}, explanation: ${parsedDate.invalidExplanation}`);
15
+ return parsedDate.set({ hour: 4 }).toMillis();
16
+ }
17
+ export function getUnixTimestampFromFormat(date, format) {
18
+ const parsedDate = DateTime.fromFormat(date, format);
19
+ if (!parsedDate.isValid)
20
+ throw new Error(`Invalid date format '${date}', expected format: ${format}, explanation: ${parsedDate.invalidExplanation}`);
21
+ return parsedDate.toMillis();
22
+ }
23
+ export function getUnixTimestampFromJSDate(date) {
24
+ const parsedDate = DateTime.fromJSDate(date);
25
+ if (!parsedDate.isValid)
26
+ throw new Error(`Invalid date '${date}', explanation: ${parsedDate.invalidExplanation}`);
27
+ return parsedDate.toMillis();
28
+ }
29
+ export function getUnixTimestampFromSeconds(seconds) {
30
+ const parsedDate = DateTime.fromSeconds(seconds);
31
+ if (!parsedDate.isValid)
32
+ throw new Error(`Invalid date '${seconds}, explanation: ${parsedDate.invalidExplanation}`);
33
+ return parsedDate.toMillis();
34
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@tmlmobilidade/utils",
3
+ "version": "20250325.1729.16",
4
+ "author": "João de Vasconcelos & Jusi Monteiro",
5
+ "license": "AGPL-3.0-or-later",
6
+ "homepage": "https://github.com/tmlmobilidade/services#readme",
7
+ "bugs": {
8
+ "url": "https://github.com/tmlmobilidade/services/issues"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/tmlmobilidade/services.git"
13
+ },
14
+ "keywords": [
15
+ "public transit",
16
+ "tml",
17
+ "transportes metropolitanos de lisboa",
18
+ "services"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "type": "module",
24
+ "module": "dist/src/index.js",
25
+ "files": [
26
+ "dist/"
27
+ ],
28
+ "types": "dist/index.d.ts",
29
+ "scripts": {
30
+ "build": "rimraf ./dist && tsc && resolve-tspaths",
31
+ "build:types": "npx tsx scripts/build-types.ts && npx rimraf ./dist && npx tsup --config tsup-types.config.ts",
32
+ "clean": "sh cleanup.sh",
33
+ "docker:local": "docker compose -f docker/local/compose.local.yaml up -d",
34
+ "docker:local-down": "docker compose -f docker/local/compose.local.yaml down",
35
+ "lint": "eslint && tsc --noEmit",
36
+ "lint:fix": "eslint --fix",
37
+ "test": "jest",
38
+ "test:prod": "jest --silent --coverage",
39
+ "test:watch": "jest --watch"
40
+ },
41
+ "dependencies": {
42
+ "luxon": "3.5.0",
43
+ "mergekit": "3.0.6",
44
+ "uuid": "11.1.0",
45
+ "zod": "3.24.2"
46
+ },
47
+ "devDependencies": {
48
+ "@carrismetropolitana/eslint": "20250303.2009.45",
49
+ "@tmlmobilidade/tsconfig": "*",
50
+ "@tmlmobilidade/types": "*",
51
+ "@types/luxon": "3.4.2",
52
+ "@types/node": "22.13.13",
53
+ "resolve-tspaths": "0.8.23",
54
+ "rimraf": "6.0.1",
55
+ "turbo": "2.4.4",
56
+ "typescript": "5.8.2"
57
+ }
58
+ }