@veloxts/core 0.8.0 → 0.8.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @veloxts/core
2
2
 
3
+ ## 0.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: add business logic primitives for B2B SaaS apps
8
+
3
9
  ## 0.8.0
4
10
 
5
11
  ### Minor Changes
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Domain error base class for business logic errors
3
+ *
4
+ * Provides a typed, abstract error class that user-land code extends to define
5
+ * domain-specific error types with structured data payloads. Extends VeloxError
6
+ * so Fastify's error handler can serialize them automatically.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * class InsufficientStock extends DomainError<{ sku: string; requested: number; available: number }> {
11
+ * readonly code = 'INSUFFICIENT_STOCK' as const;
12
+ * readonly status = 422;
13
+ * readonly message = 'Not enough inventory';
14
+ * }
15
+ *
16
+ * throw new InsufficientStock({ sku: 'ABC-123', requested: 10, available: 3 });
17
+ * ```
18
+ *
19
+ * @module errors/domain-error
20
+ */
21
+ import { VeloxError } from '../errors.js';
22
+ /**
23
+ * Branded symbol for cross-package type guard checks.
24
+ * Uses `Symbol.for` so the same symbol is shared across package boundaries.
25
+ */
26
+ declare const DOMAIN_ERROR_BRAND: unique symbol;
27
+ /**
28
+ * JSON representation of a domain error for API responses.
29
+ */
30
+ export interface DomainErrorJSON {
31
+ /** Error class name (e.g. "InsufficientStock") */
32
+ error: string;
33
+ /** Human-readable error message */
34
+ message: string;
35
+ /** HTTP status code */
36
+ statusCode: number;
37
+ /** Machine-readable error code (e.g. "INSUFFICIENT_STOCK") */
38
+ code: string;
39
+ /** Structured error data payload */
40
+ data: Record<string, unknown>;
41
+ }
42
+ /**
43
+ * Abstract base class for domain-specific business logic errors.
44
+ *
45
+ * Subclasses declare `code`, `status`, and `message` as readonly class fields.
46
+ * The constructor accepts only the typed `data` payload.
47
+ *
48
+ * @template TData - Shape of the structured data payload
49
+ */
50
+ export declare abstract class DomainError<TData extends Record<string, unknown>> extends VeloxError {
51
+ /**
52
+ * Machine-readable error code (e.g. "INSUFFICIENT_STOCK").
53
+ * Defined by each concrete subclass.
54
+ */
55
+ abstract readonly code: string;
56
+ /**
57
+ * HTTP status code for this domain error.
58
+ * Defined by each concrete subclass (e.g. 422, 403).
59
+ */
60
+ abstract readonly status: number;
61
+ /**
62
+ * Human-readable error message.
63
+ * Defined by each concrete subclass as a readonly class field.
64
+ */
65
+ abstract readonly message: string;
66
+ /**
67
+ * Structured data payload describing the error context.
68
+ */
69
+ readonly data: TData;
70
+ /**
71
+ * Brand marker for cross-package `isDomainError()` checks.
72
+ * @internal
73
+ */
74
+ readonly [DOMAIN_ERROR_BRAND]: true;
75
+ /**
76
+ * Creates a new DomainError instance.
77
+ *
78
+ * @param data - Typed data payload for the error
79
+ */
80
+ constructor(data: TData);
81
+ /**
82
+ * Converts domain error to JSON format for API responses.
83
+ * Includes `code` and `data` alongside the standard VeloxError fields.
84
+ */
85
+ toJSON(): DomainErrorJSON;
86
+ }
87
+ /**
88
+ * Type guard to check whether an unknown value is a DomainError.
89
+ *
90
+ * Uses a branded symbol (`Symbol.for('velox.domain-error')`) so the check
91
+ * works across package boundaries even when multiple copies of `@veloxts/core`
92
+ * are installed.
93
+ *
94
+ * @param value - Value to check
95
+ * @returns `true` if `value` is a DomainError instance
96
+ */
97
+ export declare function isDomainError(value: unknown): value is DomainError<Record<string, unknown>>;
98
+ export {};
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Domain error base class for business logic errors
3
+ *
4
+ * Provides a typed, abstract error class that user-land code extends to define
5
+ * domain-specific error types with structured data payloads. Extends VeloxError
6
+ * so Fastify's error handler can serialize them automatically.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * class InsufficientStock extends DomainError<{ sku: string; requested: number; available: number }> {
11
+ * readonly code = 'INSUFFICIENT_STOCK' as const;
12
+ * readonly status = 422;
13
+ * readonly message = 'Not enough inventory';
14
+ * }
15
+ *
16
+ * throw new InsufficientStock({ sku: 'ABC-123', requested: 10, available: 3 });
17
+ * ```
18
+ *
19
+ * @module errors/domain-error
20
+ */
21
+ import { VeloxError } from '../errors.js';
22
+ /**
23
+ * Branded symbol for cross-package type guard checks.
24
+ * Uses `Symbol.for` so the same symbol is shared across package boundaries.
25
+ */
26
+ const DOMAIN_ERROR_BRAND = Symbol.for('velox.domain-error');
27
+ /**
28
+ * Abstract base class for domain-specific business logic errors.
29
+ *
30
+ * Subclasses declare `code`, `status`, and `message` as readonly class fields.
31
+ * The constructor accepts only the typed `data` payload.
32
+ *
33
+ * @template TData - Shape of the structured data payload
34
+ */
35
+ export class DomainError extends VeloxError {
36
+ /**
37
+ * Structured data payload describing the error context.
38
+ */
39
+ data;
40
+ /**
41
+ * Brand marker for cross-package `isDomainError()` checks.
42
+ * @internal
43
+ */
44
+ [DOMAIN_ERROR_BRAND] = true;
45
+ /**
46
+ * Creates a new DomainError instance.
47
+ *
48
+ * @param data - Typed data payload for the error
49
+ */
50
+ constructor(data) {
51
+ // Pass placeholder values; subclass field initializers override them after super returns.
52
+ super('', 0);
53
+ this.data = data;
54
+ // Defer property synchronisation to a microtask-free post-init trick:
55
+ // We redefine `statusCode` as a getter so it always reflects the subclass's
56
+ // `status` field, which is set by the subclass's field initializers AFTER
57
+ // this constructor returns.
58
+ Object.defineProperty(this, 'statusCode', {
59
+ get() {
60
+ return this.status;
61
+ },
62
+ enumerable: true,
63
+ configurable: true,
64
+ });
65
+ // `name` should reflect the concrete subclass for stack traces and toJSON().
66
+ // Subclass field initializers haven't run yet, so we read from the constructor.
67
+ this.name = this.constructor.name;
68
+ if (Error.captureStackTrace) {
69
+ Error.captureStackTrace(this, this.constructor);
70
+ }
71
+ }
72
+ /**
73
+ * Converts domain error to JSON format for API responses.
74
+ * Includes `code` and `data` alongside the standard VeloxError fields.
75
+ */
76
+ toJSON() {
77
+ return {
78
+ error: this.name,
79
+ message: this.message,
80
+ statusCode: this.statusCode,
81
+ code: this.code,
82
+ data: this.data,
83
+ };
84
+ }
85
+ }
86
+ /**
87
+ * Type guard to check whether an unknown value is a DomainError.
88
+ *
89
+ * Uses a branded symbol (`Symbol.for('velox.domain-error')`) so the check
90
+ * works across package boundaries even when multiple copies of `@veloxts/core`
91
+ * are installed.
92
+ *
93
+ * @param value - Value to check
94
+ * @returns `true` if `value` is a DomainError instance
95
+ */
96
+ export function isDomainError(value) {
97
+ if (value == null || typeof value !== 'object') {
98
+ return false;
99
+ }
100
+ return value[DOMAIN_ERROR_BRAND] === true;
101
+ }
package/dist/index.d.ts CHANGED
@@ -21,6 +21,8 @@ export type { StartOptions } from './app.js';
21
21
  export { VeloxApp, velox, veloxApp } from './app.js';
22
22
  export type { BaseContext } from './context.js';
23
23
  export { createContext, isContext, setupContextHook, setupTestContext } from './context.js';
24
+ export type { DomainErrorJSON } from './errors/domain-error.js';
25
+ export { DomainError, isDomainError } from './errors/domain-error.js';
24
26
  export type { ConflictErrorResponse, ErrorCode, ErrorResponse, ForbiddenErrorResponse, GenericErrorResponse, InterpolationVars, NotFoundErrorResponse, ServiceUnavailableErrorResponse, TooManyRequestsErrorResponse, UnauthorizedErrorResponse, UnprocessableEntityErrorResponse, ValidationErrorResponse, VeloxCoreErrorCode, VeloxErrorCode, VeloxErrorCodeRegistry, } from './errors.js';
25
27
  export { assertNever, ConfigurationError, ConflictError, ForbiddenError, fail, isConfigurationError, isConflictError, isForbiddenError, isNotFoundError, isNotFoundErrorResponse, isServiceUnavailableError, isTooManyRequestsError, isUnauthorizedError, isUnprocessableEntityError, isValidationError, isValidationErrorResponse, isVeloxError, isVeloxFailure, logDeprecation, logWarning, NotFoundError, ServiceUnavailableError, TooManyRequestsError, UnauthorizedError, UnprocessableEntityError, ValidationError, VeloxError, VeloxFailure, } from './errors.js';
26
28
  export type { ContextPluginConfig, InferPluginOptions, PluginMetadata, PluginOptions, VeloxPlugin, } from './plugin.js';
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ const packageJson = require('../package.json');
21
21
  export const VELOX_VERSION = packageJson.version ?? '0.0.0-unknown';
22
22
  export { VeloxApp, velox, veloxApp } from './app.js';
23
23
  export { createContext, isContext, setupContextHook, setupTestContext } from './context.js';
24
+ export { DomainError, isDomainError } from './errors/domain-error.js';
24
25
  export { assertNever, ConfigurationError, ConflictError, ForbiddenError, fail, isConfigurationError, isConflictError, isForbiddenError, isNotFoundError, isNotFoundErrorResponse, isServiceUnavailableError, isTooManyRequestsError, isUnauthorizedError, isUnprocessableEntityError, isValidationError, isValidationErrorResponse, isVeloxError, isVeloxFailure, logDeprecation, logWarning, NotFoundError, ServiceUnavailableError, TooManyRequestsError, UnauthorizedError, UnprocessableEntityError, ValidationError, VeloxError, VeloxFailure, } from './errors.js';
25
26
  export { defineContextPlugin, definePlugin, isFastifyPlugin, isVeloxPlugin, validatePluginMetadata, } from './plugin.js';
26
27
  export { isValidHost, isValidPort } from './utils/config.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/core",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Fastify wrapper and plugin system for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,7 +29,7 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "fastify": "5.7.4",
32
+ "fastify": "5.8.2",
33
33
  "fastify-plugin": "5.1.0",
34
34
  "picocolors": "1.1.1"
35
35
  },
@@ -43,10 +43,10 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@fastify/static": "9.0.0",
46
- "@types/node": "25.2.3",
47
- "@vitest/coverage-v8": "4.0.18",
46
+ "@types/node": "25.5.0",
47
+ "@vitest/coverage-v8": "4.1.0",
48
48
  "typescript": "5.9.3",
49
- "vitest": "4.0.18"
49
+ "vitest": "4.1.0"
50
50
  },
51
51
  "keywords": [
52
52
  "velox",