@kurdel/common 0.1.0-beta.2 → 0.1.0-beta.4

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 CHANGED
@@ -1,5 +1,28 @@
1
1
  # @kurdel/common
2
2
 
3
+ ## Environment configuration
4
+
5
+ Define typed environment variables and validate all of them before application
6
+ startup:
7
+
8
+ ```ts
9
+ import { env, loadEnv } from '@kurdel/common';
10
+
11
+ const environment = loadEnv({
12
+ NODE_ENV: env.enum(['development', 'test', 'production'], {
13
+ default: 'development',
14
+ }),
15
+ PORT: env.number({ default: 3000, integer: true, min: 1, max: 65_535 }),
16
+ DATABASE_URL: env.string({ minLength: 1 }),
17
+ ENABLE_AUDIT: env.boolean({ default: true }),
18
+ SENTRY_DSN: env.optional(env.string()),
19
+ });
20
+ ```
21
+
22
+ The returned object is inferred from the schema. Invalid configuration throws
23
+ one `EnvironmentValidationError` containing every affected variable and safe
24
+ reason, without including rejected values.
25
+
3
26
  Shared TypeScript types, interfaces, and utilities used by Kurdel packages.
4
27
  Application code normally receives this package transitively through the
5
28
  framework packages that depend on it.
@@ -9,6 +9,7 @@
9
9
  * Properties:
10
10
  * - `id` — public stable identity of the user (string or number).
11
11
  * - `roles` — zero or more logical roles (e.g., `"admin"`, `"user"`, `"system"`).
12
+ * - `permissions` — optional capabilities resolved for the current identity.
12
13
  * - Additional fields may be attached by strategies (JWT payload, profile data, etc.).
13
14
  *
14
15
  * @remarks
@@ -18,6 +19,7 @@
18
19
  export interface AuthUser {
19
20
  id: string | number;
20
21
  roles: string[];
22
+ permissions?: string[];
21
23
  [key: string]: unknown;
22
24
  }
23
25
  /** Metadata about the credential used for the current authentication. */
@@ -0,0 +1,39 @@
1
+ export type EnvironmentSource = Readonly<Record<string, string | undefined>>;
2
+ export type EnvironmentIssue = {
3
+ key: string;
4
+ message: string;
5
+ };
6
+ export declare class EnvironmentValidationError extends Error {
7
+ readonly issues: readonly EnvironmentIssue[];
8
+ constructor(issues: readonly EnvironmentIssue[]);
9
+ }
10
+ export interface EnvironmentVariable<T> {
11
+ parse(value: string | undefined): T;
12
+ }
13
+ type BaseOptions<T> = {
14
+ default?: T;
15
+ };
16
+ type StringOptions = BaseOptions<string> & {
17
+ trim?: boolean;
18
+ minLength?: number;
19
+ maxLength?: number;
20
+ };
21
+ type NumberOptions = BaseOptions<number> & {
22
+ integer?: boolean;
23
+ min?: number;
24
+ max?: number;
25
+ };
26
+ export declare const env: {
27
+ optional<T>(definition: EnvironmentVariable<T>): EnvironmentVariable<T | undefined>;
28
+ string(options?: StringOptions): EnvironmentVariable<string>;
29
+ number(options?: NumberOptions): EnvironmentVariable<number>;
30
+ boolean(options?: BaseOptions<boolean>): EnvironmentVariable<boolean>;
31
+ enum<const T extends readonly [string, ...string[]]>(values: T, options?: BaseOptions<T[number]>): EnvironmentVariable<T[number]>;
32
+ };
33
+ type EnvironmentSchema = Record<string, EnvironmentVariable<unknown>>;
34
+ export type InferEnvironment<T extends EnvironmentSchema> = {
35
+ [K in keyof T]: T[K] extends EnvironmentVariable<infer V> ? V : never;
36
+ };
37
+ /** Parses all configured variables and reports every validation issue at once. */
38
+ export declare function loadEnv<const T extends EnvironmentSchema>(schema: T, source?: EnvironmentSource): InferEnvironment<T>;
39
+ export {};
@@ -0,0 +1,101 @@
1
+ export class EnvironmentValidationError extends Error {
2
+ constructor(issues) {
3
+ super([
4
+ 'Invalid environment configuration:',
5
+ ...issues.map(issue => `- ${issue.key}: ${issue.message}`),
6
+ ].join('\n'));
7
+ this.issues = issues;
8
+ this.name = 'EnvironmentValidationError';
9
+ }
10
+ }
11
+ function requiredValue(value, options) {
12
+ if (value !== undefined && value.trim() !== '')
13
+ return value;
14
+ if (Object.prototype.hasOwnProperty.call(options, 'default'))
15
+ return options.default;
16
+ throw new Error('is required');
17
+ }
18
+ function variable(parse) {
19
+ return { parse };
20
+ }
21
+ export const env = {
22
+ optional(definition) {
23
+ return variable(value => value === undefined || value.trim() === ''
24
+ ? undefined
25
+ : definition.parse(value));
26
+ },
27
+ string(options = {}) {
28
+ return variable(value => {
29
+ const resolved = requiredValue(value, options);
30
+ if (typeof resolved !== 'string')
31
+ return resolved;
32
+ const parsed = options.trim === false ? resolved : resolved.trim();
33
+ if (options.minLength !== undefined && parsed.length < options.minLength) {
34
+ throw new Error(`must contain at least ${options.minLength} characters`);
35
+ }
36
+ if (options.maxLength !== undefined && parsed.length > options.maxLength) {
37
+ throw new Error(`must contain at most ${options.maxLength} characters`);
38
+ }
39
+ return parsed;
40
+ });
41
+ },
42
+ number(options = {}) {
43
+ return variable(value => {
44
+ const resolved = requiredValue(value, options);
45
+ const parsed = typeof resolved === 'number' ? resolved : Number(resolved);
46
+ if (!Number.isFinite(parsed))
47
+ throw new Error('must be a finite number');
48
+ if (options.integer && !Number.isInteger(parsed))
49
+ throw new Error('must be an integer');
50
+ if (options.min !== undefined && parsed < options.min) {
51
+ throw new Error(`must be greater than or equal to ${options.min}`);
52
+ }
53
+ if (options.max !== undefined && parsed > options.max) {
54
+ throw new Error(`must be less than or equal to ${options.max}`);
55
+ }
56
+ return parsed;
57
+ });
58
+ },
59
+ boolean(options = {}) {
60
+ return variable(value => {
61
+ const resolved = requiredValue(value, options);
62
+ if (typeof resolved === 'boolean')
63
+ return resolved;
64
+ const normalized = resolved.trim().toLowerCase();
65
+ if (['true', '1', 'yes', 'on'].includes(normalized))
66
+ return true;
67
+ if (['false', '0', 'no', 'off'].includes(normalized))
68
+ return false;
69
+ throw new Error('must be a boolean');
70
+ });
71
+ },
72
+ enum(values, options = {}) {
73
+ return variable(value => {
74
+ const resolved = requiredValue(value, options);
75
+ const normalized = typeof resolved === 'string' ? resolved.trim() : resolved;
76
+ if (values.includes(normalized))
77
+ return normalized;
78
+ throw new Error(`must be one of: ${values.join(', ')}`);
79
+ });
80
+ },
81
+ };
82
+ /** Parses all configured variables and reports every validation issue at once. */
83
+ export function loadEnv(schema, source = process.env) {
84
+ const output = {};
85
+ const issues = [];
86
+ for (const [key, definition] of Object.entries(schema)) {
87
+ try {
88
+ output[key] = definition.parse(source[key]);
89
+ }
90
+ catch (error) {
91
+ issues.push({
92
+ key,
93
+ message: error instanceof Error ? error.message : 'is invalid',
94
+ });
95
+ }
96
+ }
97
+ if (issues.length > 0)
98
+ throw new EnvironmentValidationError(issues);
99
+ return output;
100
+ }
101
+ //# sourceMappingURL=environment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment.js","sourceRoot":"","sources":["../src/environment.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export * from './auth-user.js';
4
4
  export * from './http-response-extended.js';
5
5
  export * from './json-loader.js';
6
6
  export * from './url.js';
7
+ export * from './environment.js';
package/lib/index.js CHANGED
@@ -4,4 +4,5 @@ export * from './auth-user.js';
4
4
  export * from './http-response-extended.js';
5
5
  export * from './json-loader.js';
6
6
  export * from './url.js';
7
+ export * from './environment.js';
7
8
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kurdel/common",
3
- "version": "0.1.0-beta.2",
3
+ "version": "0.1.0-beta.4",
4
4
  "description": "Shared types and utilities for Kurdel packages",
5
5
  "type": "module",
6
6
  "exports": {
@@ -13,7 +13,7 @@
13
13
  ],
14
14
  "scripts": {
15
15
  "prepack": "npm run build",
16
- "test": "echo \"No tests yet\"",
16
+ "test": "vitest run",
17
17
  "clean": "rimraf lib ../../.cache/tsconfig.common.build.tsbuildinfo",
18
18
  "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
19
19
  "build:force": "npm run clean && tsc -p tsconfig.build.json --force && tsc-alias -p tsconfig.build.json"