@gravito/mass 3.0.1 → 3.0.3

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,23 +1,46 @@
1
- # @gravito/mass
1
+ # @gravito/mass ⚖️
2
2
 
3
- TypeBox-based validation for Gravito. High-performance schema validation with full TypeScript support.
3
+ > High-performance, TypeBox-powered schema validation for Gravito Galaxy Architecture.
4
4
 
5
- ## Features
5
+ `@gravito/mass` provides the "weight" of data integrity to your Gravito applications. Built on top of **TypeBox**, it offers ultra-fast runtime validation with full TypeScript type inference, designed to work seamlessly with the **Photon** HTTP engine.
6
6
 
7
- - **Fast validation**: TypeBox-powered validators with strong runtime performance
8
- - **Full TypeScript support**: Type inference without manual typings
9
- - **Photon integration**: Works seamlessly with Photon validation middleware
10
- - **Multiple sources**: Validate JSON, query, params, and form data
7
+ ## Key Features
11
8
 
12
- ## Installation
9
+ - 🚀 **Performance-First**: Built on TypeBox, it generates ultra-fast validators that outperform standard libraries like Zod.
10
+ - 🌌 **Galaxy-Ready**: The "Data Quality Layer" ensuring integrity across all Gravito Satellites and Orbits.
11
+ - 🛡️ **Full Type Safety**: Zero-config type inference—your schemas are your TypeScript types.
12
+ - 📡 **Beam Integration**: Share schemas between backend and frontend for end-to-end type-safe RPC.
13
+ - 🔌 **Photon Integration**: Native middleware for `@gravito/photon` to validate JSON, query, params, and form data.
14
+ - 📦 **Binary (CBOR) Support**: Optimized validation for binary protocols in high-performance microservices.
15
+
16
+ ## 🌌 Role in Galaxy Architecture
17
+
18
+ In the **Gravito Galaxy Architecture**, Mass serves as the **Data Quality Layer (Immune System)**.
19
+
20
+ - **Sensing Filter**: Acts as the first line of defense in the `Photon` Sensing Layer, filtering out malformed or malicious data before it reaches your business logic.
21
+ - **Contract Definition**: Provides the shared language (Schemas) used by `Beam` to ensure that Satellites and clients communicate with perfect understanding.
22
+ - **Type Propagation**: Injects strong types from the edge of the network into the deepest parts of your IoC container.
23
+
24
+ ```mermaid
25
+ graph LR
26
+ Request([Request]) --> Mass{Mass Filter}
27
+ Mass -- "Valid" --> Satellite[Satellite Business Logic]
28
+ Mass -- "Invalid" --> Error[400 Bad Request]
29
+ Satellite -- "Beam RPC" --> Client([Typed Client])
30
+ Mass -.->|Schema| Client
31
+ ```
32
+
33
+ ## 📦 Installation
13
34
 
14
35
  ```bash
15
36
  bun add @gravito/mass
16
37
  ```
17
38
 
18
- ## Quick Start
39
+ ## 🚀 Quick Start
19
40
 
20
- ### JSON validation
41
+ ### Basic JSON Validation
42
+
43
+ Define a schema using `Schema` and apply it to your routes using the `validate` middleware.
21
44
 
22
45
  ```typescript
23
46
  import { Photon } from '@gravito/photon'
@@ -25,40 +48,39 @@ import { Schema, validate } from '@gravito/mass'
25
48
 
26
49
  const app = new Photon()
27
50
 
28
- app.post('/login',
29
- validate('json', Schema.Object({
30
- username: Schema.String(),
31
- password: Schema.String()
32
- })),
51
+ const CreateUserSchema = Schema.Object({
52
+ username: Schema.String({ minLength: 3 }),
53
+ email: Schema.String({ format: 'email' }),
54
+ age: Schema.Number({ minimum: 18 })
55
+ })
56
+
57
+ app.post('/users',
58
+ validate('json', CreateUserSchema),
33
59
  (c) => {
34
- const { username } = c.req.valid('json')
35
- return c.json({ success: true, message: `Welcome ${username}` })
60
+ // Data is fully typed as { username: string; email: string; age: number }
61
+ const user = c.req.valid('json')
62
+ return c.json({ success: true, data: user })
36
63
  }
37
64
  )
38
65
  ```
39
66
 
40
- ### Query validation
67
+ ### Validating Different Sources
68
+
69
+ Mass can validate data from various parts of the HTTP request:
41
70
 
42
71
  ```typescript
43
- app.get('/search',
44
- validate('query', Schema.Object({
45
- q: Schema.String(),
46
- page: Schema.Optional(Schema.Number())
47
- })),
72
+ // Query Parameters
73
+ app.get('/search',
74
+ validate('query', Schema.Object({ q: Schema.String() })),
48
75
  (c) => {
49
- const { q, page } = c.req.valid('query')
50
- return c.json({ query: q, page: page ?? 1 })
76
+ const { q } = c.req.valid('query')
77
+ return c.text(`Searching for: ${q}`)
51
78
  }
52
79
  )
53
- ```
54
-
55
- ### Route param validation
56
80
 
57
- ```typescript
81
+ // URL Parameters
58
82
  app.get('/users/:id',
59
- validate('param', Schema.Object({
60
- id: Schema.String({ pattern: '^[0-9]+$' })
61
- })),
83
+ validate('param', Schema.Object({ id: Schema.Number() })),
62
84
  (c) => {
63
85
  const { id } = c.req.valid('param')
64
86
  return c.json({ userId: id })
@@ -66,65 +88,77 @@ app.get('/users/:id',
66
88
  )
67
89
  ```
68
90
 
69
- ## Schema Builder
91
+ ## Advanced Patterns
92
+
93
+ ### Partial Updates (PATCH)
70
94
 
71
- `Schema` exposes TypeBox constructors:
95
+ Use the `partial()` utility to make all properties of a schema optional, perfect for update endpoints.
72
96
 
73
97
  ```typescript
74
- import { Schema } from '@gravito/mass'
98
+ import { partial } from '@gravito/mass'
75
99
 
76
- Schema.String()
77
- Schema.Number()
78
- Schema.Boolean()
79
- Schema.Array(Schema.String())
100
+ const UpdateUserSchema = partial(CreateUserSchema)
80
101
 
81
- Schema.Object({
82
- name: Schema.String(),
83
- age: Schema.Number()
84
- })
85
-
86
- Schema.Optional(Schema.String())
87
- Schema.String({ default: 'hello' })
88
- Schema.String({ minLength: 2, maxLength: 100 })
89
- Schema.Number({ minimum: 0, maximum: 100 })
90
- Schema.String({ format: 'email' })
102
+ app.patch('/users/:id',
103
+ validate('json', UpdateUserSchema),
104
+ (c) => {
105
+ const updates = c.req.valid('json')
106
+ return c.json({ updated: updates })
107
+ }
108
+ )
91
109
  ```
92
110
 
93
- ## Beam Client Integration
111
+ ### Custom Error Handling
94
112
 
95
- When you compose routes with `app.route()`, you get full type inference for the client:
113
+ Override the default 400 response with your own error format using the validation hook.
96
114
 
97
115
  ```typescript
98
- // app.ts
99
- import { Photon } from '@gravito/photon'
100
- import { userRoute } from './routes/user'
116
+ app.post('/strict-endpoint',
117
+ validate('json', schema, (result, c) => {
118
+ if (!result.success) {
119
+ return c.json({
120
+ code: 'VAL_ERR',
121
+ errors: result.errors.map(e => ({ field: e.path, msg: e.message }))
122
+ }, 422)
123
+ }
124
+ }),
125
+ (c) => c.text('Success')
126
+ )
127
+ ```
101
128
 
102
- const app = new Photon()
103
- const routes = app.route('/api/users', userRoute)
129
+ ## 🧩 API Reference
104
130
 
105
- export default app
106
- export type AppRoutes = typeof routes
107
- ```
131
+ ### `validate(source, schema, hook?)`
132
+ Main middleware for schema enforcement.
133
+ - `source`: `'json' | 'query' | 'param' | 'form'`
134
+ - `schema`: A TypeBox schema instance.
135
+ - `hook`: `(result, context) => Response | undefined`
108
136
 
109
- ```typescript
110
- // client.ts
111
- import { createBeam } from '@gravito/beam'
112
- import type { AppRoutes } from './types'
137
+ ### `Schema`
138
+ The central builder for defining data structures. Re-exports all TypeBox builders.
139
+ - `Schema.String()`
140
+ - `Schema.Number()`
141
+ - `Schema.Boolean()`
142
+ - `Schema.Object({ ... })`
143
+ - `Schema.Array(...)`
144
+ - `Schema.Optional(...)`
113
145
 
114
- export const createClient = (baseUrl: string) => {
115
- return createBeam<AppRoutes>(baseUrl)
116
- }
146
+ ### `partial(schema)`
147
+ Recursively makes all properties in an object schema optional.
117
148
 
118
- const client = createClient('http://localhost:3000')
119
- const result = await client.api.users.login.$post({
120
- json: { username: 'user', password: 'pass' }
121
- })
122
- ```
149
+ ## 📚 Documentation
150
+
151
+ Detailed guides and references for the Galaxy Architecture:
152
+
153
+ - [🏗️ **Architecture Overview**](./README.md) — Data integrity and schema validation.
154
+ - [📐 **Schema Design**](./doc/SCHEMA_DESIGN.md) — **NEW**: Best practices for domain-specific schemas and composition.
155
+ - [🛡️ **Validation Strategy**](./doc/VALIDATION_STRATEGY.md) — **NEW**: Multi-source validation, hooks, and performance tuning.
156
+ - [🔌 **Photon Integration**](#-photon-integration) — Native middleware for request validation.
123
157
 
124
- ## Performance Notes
158
+ ## 🤝 Contributing
125
159
 
126
- TypeBox generates validators at build-time for faster runtime performance, smaller bundles, and strong TypeScript inference.
160
+ We welcome contributions! Please see our [Contributing Guide](../../CONTRIBUTING.md) for details.
127
161
 
128
- ## License
162
+ ## 📄 License
129
163
 
130
- MIT
164
+ MIT © Carl Lee
package/README.zh-TW.md CHANGED
@@ -1,14 +1,29 @@
1
- # @gravito/mass
1
+ # @gravito/mass ⚖️
2
2
 
3
- > Gravito TypeBox 驗證模組,提供高效能且完整的 TypeScript 支援。
3
+ > 基於 TypeBox 的高效能 Schema 驗證工具,專為 Gravito Galaxy 架構設計。
4
4
 
5
- ## 安裝
5
+ `@gravito/mass` 為您的 Gravito 應用程式提供數據完整性的「重量」。基於 **TypeBox** 構建,它提供了極快的執行期驗證速度與完整的 TypeScript 類型推導,並與 **Photon** HTTP 引擎無縫整合。
6
+
7
+ ## 🌟 核心特性
8
+
9
+ - **🚀 效能優先**:利用 TypeBox 的編譯期驗證器生成技術,實現近乎零負擔的執行期效能。
10
+ - **🛡️ 完整類型安全**:自動進行 TypeScript 類型推導 —— 無需分別維護 Interface 與 Schema。
11
+ - **🔌 Photon 深度整合**:提供原生中間件,支援驗證 JSON、Query、URL 參數及表單數據。
12
+ - **🛠️ Schema 實用工具**:提供如 `partial()` 等進階輔助函式,輕鬆建立 PATCH 接口所需的 Schema。
13
+ - **🪝 靈活的 Hook 機制**:可攔截驗證結果,自定義錯誤回應格式或紀錄日誌。
14
+ - **📦 Galaxy 架構相容**:遵循 Gravito 的模組化哲學,保持依賴精簡且高效。
15
+
16
+ ## 📦 安裝
6
17
 
7
18
  ```bash
8
19
  bun add @gravito/mass
9
20
  ```
10
21
 
11
- ## 快速開始
22
+ ## 🚀 快速上手
23
+
24
+ ### 基礎 JSON 驗證
25
+
26
+ 使用 `Schema` 定義數據結構,並透過 `validate` 中間件應用於路由。
12
27
 
13
28
  ```typescript
14
29
  import { Photon } from '@gravito/photon'
@@ -16,14 +31,108 @@ import { Schema, validate } from '@gravito/mass'
16
31
 
17
32
  const app = new Photon()
18
33
 
19
- app.post('/login',
20
- validate('json', Schema.Object({
21
- username: Schema.String(),
22
- password: Schema.String()
23
- })),
34
+ const CreateUserSchema = Schema.Object({
35
+ username: Schema.String({ minLength: 3 }),
36
+ email: Schema.String({ format: 'email' }),
37
+ age: Schema.Number({ minimum: 18 })
38
+ })
39
+
40
+ app.post('/users',
41
+ validate('json', CreateUserSchema),
42
+ (c) => {
43
+ // 數據已自動推導類型為 { username: string; email: string; age: number }
44
+ const user = c.req.valid('json')
45
+ return c.json({ success: true, data: user })
46
+ }
47
+ )
48
+ ```
49
+
50
+ ### 驗證不同數據源
51
+
52
+ Mass 可以驗證 HTTP 請求中不同位置的數據:
53
+
54
+ ```typescript
55
+ // 驗證 Query 參數
56
+ app.get('/search',
57
+ validate('query', Schema.Object({ q: Schema.String() })),
24
58
  (c) => {
25
- const { username } = c.req.valid('json')
26
- return c.json({ success: true, message: `Welcome ${username}` })
59
+ const { q } = c.req.valid('query')
60
+ return c.text(`正在搜尋:${q}`)
61
+ }
62
+ )
63
+
64
+ // 驗證 URL 參數
65
+ app.get('/users/:id',
66
+ validate('param', Schema.Object({ id: Schema.Number() })),
67
+ (c) => {
68
+ const { id } = c.req.valid('param')
69
+ return c.json({ userId: id })
27
70
  }
28
71
  )
29
72
  ```
73
+
74
+ ## ⏳ 進階模式
75
+
76
+ ### 部分更新 (PATCH)
77
+
78
+ 使用 `partial()` 工具將 Schema 中的所有屬性轉為可選,非常適合用於更新接口。
79
+
80
+ ```typescript
81
+ import { partial } from '@gravito/mass'
82
+
83
+ const UpdateUserSchema = partial(CreateUserSchema)
84
+
85
+ app.patch('/users/:id',
86
+ validate('json', UpdateUserSchema),
87
+ (c) => {
88
+ const updates = c.req.valid('json')
89
+ return c.json({ updated: updates })
90
+ }
91
+ )
92
+ ```
93
+
94
+ ### 自定義錯誤處理
95
+
96
+ 透過驗證 Hook 覆蓋預設的 400 回應,提供符合您需求的錯誤格式。
97
+
98
+ ```typescript
99
+ app.post('/strict-endpoint',
100
+ validate('json', schema, (result, c) => {
101
+ if (!result.success) {
102
+ return c.json({
103
+ code: 'VAL_ERR',
104
+ errors: result.errors.map(e => ({ field: e.path, msg: e.message }))
105
+ }, 422)
106
+ }
107
+ }),
108
+ (c) => c.text('成功')
109
+ )
110
+ ```
111
+
112
+ ## 🧩 API 參考
113
+
114
+ ### `validate(source, schema, hook?)`
115
+ 強制執行 Schema 驗證的主要中間件。
116
+ - `source`: `'json' | 'query' | 'param' | 'form'`
117
+ - `schema`: TypeBox Schema 實例。
118
+ - `hook`: `(result, context) => Response | undefined`
119
+
120
+ ### `Schema`
121
+ 定義數據結構的核心工具,重新導出了所有 TypeBox 的建構子。
122
+ - `Schema.String()`
123
+ - `Schema.Number()`
124
+ - `Schema.Boolean()`
125
+ - `Schema.Object({ ... })`
126
+ - `Schema.Array(...)`
127
+ - `Schema.Optional(...)`
128
+
129
+ ### `partial(schema)`
130
+ 遞迴地將物件 Schema 中的所有屬性設為可選。
131
+
132
+ ## 🤝 參與貢獻
133
+
134
+ 我們歡迎任何形式的貢獻!詳細資訊請參閱 [貢獻指南](../../CONTRIBUTING.md)。
135
+
136
+ ## 📄 開源授權
137
+
138
+ MIT © Carl Lee
@@ -0,0 +1,222 @@
1
+ import type { GravitoContext, GravitoMiddleware } from '@gravito/core';
2
+ import type { TBoolean, TInteger, TNumber, TSchema } from '@sinclair/typebox';
3
+ /**
4
+ * String to Number Coercion Helper.
5
+ *
6
+ * Converts a string value from query or params to a JavaScript Number.
7
+ *
8
+ * @param value - The string value to coerce
9
+ * @returns The converted number, or NaN if conversion fails
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * coerceNumber('123') // 123
14
+ * coerceNumber('12.34') // 12.34
15
+ * coerceNumber('abc') // NaN
16
+ * ```
17
+ */
18
+ export declare function coerceNumber(value: string): number;
19
+ /**
20
+ * String to Integer Coercion Helper.
21
+ *
22
+ * Converts a string value from query or params to a JavaScript Number (Integer).
23
+ *
24
+ * @param value - The string value to coerce
25
+ * @returns The converted integer, or NaN if conversion fails
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * coerceInteger('123') // 123
30
+ * coerceInteger('12.99') // 12
31
+ * coerceInteger('abc') // NaN
32
+ * ```
33
+ */
34
+ export declare function coerceInteger(value: string): number;
35
+ /**
36
+ * String to Boolean Coercion Helper.
37
+ *
38
+ * Converts a string value from query or params to a JavaScript Boolean.
39
+ * Supports multiple common boolean string representations.
40
+ *
41
+ * @param value - The string value to coerce
42
+ * @returns The converted boolean value
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * coerceBoolean('true') // true
47
+ * coerceBoolean('1') // true
48
+ * coerceBoolean('yes') // true
49
+ * coerceBoolean('false') // false
50
+ * coerceBoolean('0') // false
51
+ * coerceBoolean('no') // false
52
+ * coerceBoolean('') // false
53
+ * ```
54
+ */
55
+ export declare function coerceBoolean(value: string): boolean;
56
+ /**
57
+ * String to Date Coercion Helper.
58
+ *
59
+ * Converts a string date (ISO 8601) from query or params to a JavaScript Date object.
60
+ *
61
+ * @param value - The string value (ISO 8601 format)
62
+ * @returns The converted Date object, or Invalid Date if conversion fails
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * coerceDate('2024-01-15') // Date object
67
+ * coerceDate('2024-01-15T10:30:00Z') // Date object
68
+ * coerceDate('invalid') // Invalid Date
69
+ * ```
70
+ */
71
+ export declare function coerceDate(value: string): Date;
72
+ /**
73
+ * Creates a Number Schema with Automatic Coercion.
74
+ *
75
+ * Builds a TypeBox Number schema that automatically converts string input to number before validation.
76
+ * Suitable for query and param validation where data sources are string-based.
77
+ *
78
+ * @param options - TypeBox Number schema options
79
+ * @returns TypeBox Number schema (with automatic coercion)
80
+ *
81
+ * @example Query Parameter Coercion
82
+ * ```typescript
83
+ * const searchSchema = Schema.Object({
84
+ * page: CoercibleNumber({ minimum: 1, default: 1 }),
85
+ * limit: CoercibleNumber({ minimum: 1, maximum: 100, default: 20 })
86
+ * })
87
+ *
88
+ * app.get('/search', validate('query', searchSchema), (c) => {
89
+ * const { page, limit } = c.req.valid('query')
90
+ * // page and limit are typed as number
91
+ * return c.json({ page, limit })
92
+ * })
93
+ * ```
94
+ */
95
+ export declare function CoercibleNumber(options?: Partial<TNumber['properties']> & {
96
+ default?: number;
97
+ }): TNumber;
98
+ /**
99
+ * Creates an Integer Schema with Automatic Coercion.
100
+ *
101
+ * Builds a TypeBox Integer schema that automatically converts string input to integer before validation.
102
+ *
103
+ * @param options - TypeBox Integer schema options
104
+ * @returns TypeBox Integer schema (with automatic coercion)
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * const schema = Schema.Object({
109
+ * userId: CoercibleInteger({ minimum: 1 })
110
+ * })
111
+ * ```
112
+ */
113
+ export declare function CoercibleInteger(options?: Partial<TInteger['properties']> & {
114
+ default?: number;
115
+ }): TInteger;
116
+ /**
117
+ * Creates a Boolean Schema with Automatic Coercion.
118
+ *
119
+ * Builds a TypeBox Boolean schema that automatically converts string input to boolean before validation.
120
+ *
121
+ * @param options - TypeBox Boolean schema options
122
+ * @returns TypeBox Boolean schema (with automatic coercion)
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * const schema = Schema.Object({
127
+ * active: CoercibleBoolean({ default: false }),
128
+ * published: CoercibleBoolean()
129
+ * })
130
+ * ```
131
+ */
132
+ export declare function CoercibleBoolean(options?: Partial<TBoolean['properties']> & {
133
+ default?: boolean;
134
+ }): TBoolean;
135
+ /**
136
+ * Performs Automatic Data Coercion.
137
+ *
138
+ * Traverses the object and automatically converts string data to corresponding types
139
+ * based on the schema definition. Primarily used for pre-processing query and param data.
140
+ *
141
+ * @param data - The raw data object
142
+ * @param schema - The TypeBox schema definition
143
+ * @returns The coerced data object
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * const schema = Type.Object({
148
+ * page: Type.Number(),
149
+ * limit: Type.Number(),
150
+ * active: Type.Boolean()
151
+ * })
152
+ *
153
+ * const raw = { page: '1', limit: '20', active: 'true' }
154
+ * const coerced = coerceData(raw, schema)
155
+ * // { page: 1, limit: 20, active: true }
156
+ * ```
157
+ */
158
+ export declare function coerceData<T extends TSchema>(data: unknown, schema: T): unknown;
159
+ /**
160
+ * Creates Validation Middleware with Automatic Coercion.
161
+ *
162
+ * Extends the standard validate middleware to automatically convert string data to appropriate
163
+ * types before validation. Especially useful for query and param validation where data sources
164
+ * are string-based.
165
+ *
166
+ * @template T - TypeBox schema type
167
+ *
168
+ * @param source - Validation source (recommend using 'query' or 'param')
169
+ * @param schema - TypeBox schema definition
170
+ * @param hook - Optional validation result hook
171
+ * @returns Gravito middleware handler
172
+ *
173
+ * @example Query Parameter Coercion
174
+ * ```typescript
175
+ * const searchSchema = Type.Object({
176
+ * page: Type.Number({ minimum: 1 }),
177
+ * limit: Type.Number({ minimum: 1, maximum: 100 }),
178
+ * active: Type.Optional(Type.Boolean())
179
+ * })
180
+ *
181
+ * app.get('/search',
182
+ * validateWithCoercion('query', searchSchema),
183
+ * (c) => {
184
+ * const { page, limit, active } = c.req.valid('query')
185
+ * // All values are automatically coerced to correct types
186
+ * return c.json({ page, limit, active })
187
+ * }
188
+ * )
189
+ * ```
190
+ *
191
+ * @example Route Parameter Coercion
192
+ * ```typescript
193
+ * const paramSchema = Type.Object({
194
+ * id: Type.Number({ minimum: 1 })
195
+ * })
196
+ *
197
+ * app.get('/users/:id',
198
+ * validateWithCoercion('param', paramSchema),
199
+ * (c) => {
200
+ * const { id } = c.req.valid('param')
201
+ * // id converted from string to number
202
+ * return c.json({ userId: id })
203
+ * }
204
+ * )
205
+ * ```
206
+ *
207
+ * @example With Custom Error Handling
208
+ * ```typescript
209
+ * app.get('/items',
210
+ * validateWithCoercion('query', querySchema, (result, c) => {
211
+ * if (!result.success) {
212
+ * return c.json({
213
+ * error: 'Invalid query parameters',
214
+ * details: result.errors
215
+ * }, 400)
216
+ * }
217
+ * }),
218
+ * handler
219
+ * )
220
+ * ```
221
+ */
222
+ export declare function validateWithCoercion<T extends TSchema>(source: 'json' | 'query' | 'param' | 'form', schema: T, hook?: (result: unknown, c: GravitoContext) => Response | Promise<Response> | undefined): GravitoMiddleware;