@3lineas/grooveskills 1.7.2 → 1.8.0

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.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: valibot-validation
3
- description: Schema validation at system boundaries with Valibot (* as v from "valibot"). Strict type inference (InferInput, InferOutput), input sanitization, pipeline transformations, and courteous user-facing error messages. TRIGGER when defining API request/response schemas, validating forms, parsing external payloads, env configs, or when the project imports 'valibot'.
3
+ description: Schema validation at system boundaries with Valibot (* as v from "valibot"). Strict type inference (InferInput, InferOutput), input sanitization, pipeline transformations, bidirectional DTO isolation, and courteous user-facing error messages. TRIGGER when defining API request/response schemas, validating forms, parsing external payloads, env configs, or when the project imports 'valibot'.
4
4
  ---
5
5
 
6
- # Valibot Schema Validation Standards
6
+ # Valibot Schema Validation & DTO Standards
7
7
 
8
- Guidelines for high-performance, modular schema validation at system boundaries using **Valibot** (`* as v from "valibot"`).
8
+ Guidelines for high-performance, modular schema validation at system boundaries and atomic Data Transfer Objects (DTOs) using **Valibot** (`* as v from "valibot"`).
9
9
 
10
10
  ---
11
11
 
@@ -14,29 +14,55 @@ Guidelines for high-performance, modular schema validation at system boundaries
14
14
  1. **Namespace Import `* as v from "valibot"`**:
15
15
  - Always import Valibot as namespace `import * as v from "valibot";` to prevent naming collisions and keep schemas consistent.
16
16
 
17
- 2. **Validate Every Boundary (Zero Unchecked Inputs)**:
18
- - Validate every HTTP body, query string, route param, environment variable, and database response crossing boundary lines.
17
+ 2. **Schema & DTO Isolation (1 Schema per File)**:
18
+ - DTO and Schema files (`@/dtos/{domain}/{name}.dto.ts` or `@/schemas/{domain}/`) contain **ONLY** Valibot schemas (`v.object({...})`).
19
+ - **NEVER** declare TypeScript `type` or `interface` inside a DTO/Schema file. Inferred types belong in `@/types/{domain}/`.
19
20
 
20
- 3. **Pipelines with Polite Error Messages (`v.pipe`)**:
21
+ 3. **Bidirectional Validation (Input + Output Whitelisting)**:
22
+ - **Input**: Validate request bodies, query params, and route params via validators (`vValidator` / `sValidator`).
23
+ - **Output**: Serialize and whitelist outgoing responses with output schemas (`StoreResponseDto`) to eliminate sensitive fields (`passwordHash`, private tokens) and format values (e.g. ISO date conversions).
24
+
25
+ 4. **Pipelines with Polite Error Messages (`v.pipe`)**:
21
26
  - Chain schema constraints using `v.pipe(...)`.
22
27
  - Provide clear, user-facing error messages on every validation step following the `ux-writing` skill guidelines.
23
28
 
24
- 4. **Explicit Type Inference (`v.InferInput` & `v.InferOutput`)**:
25
- - Export inferred types for transformations.
29
+ 5. **Explicit Type Inference (`v.InferInput` & `v.InferOutput`)**:
26
30
  - Use `v.InferOutput<typeof schema>` for validated downstream data, and `v.InferInput<typeof schema>` for raw form inputs.
27
31
 
28
- 5. **`v.safeParse` vs `v.parse` Discipline**:
32
+ 6. **`v.safeParse` vs `v.parse` Discipline**:
29
33
  - Use `v.safeParse` when building user-friendly forms or API endpoints to format issues into polite error responses without throwing.
30
34
  - Use `v.parse` for critical startup configurations (like environment variables) where throwing stops catastrophic initialization.
31
35
 
36
+ 7. **Hard Limit of <200 Lines**:
37
+ - Keep atomic DTO and schema definitions concise and focused (target: 20-60 lines).
38
+
39
+ ---
40
+
41
+ ## 2. Directory Taxonomy
42
+
43
+ ```text
44
+ src/
45
+ ├── dtos/ (or schemas/)
46
+ │ └── {domain}/
47
+ │ ├── create-{entity}.dto.ts # Request Body Schema
48
+ │ ├── {entity}-param.dto.ts # URL Param Schema
49
+ │ ├── {entity}-query.dto.ts # Query Filter Schema
50
+ │ └── {entity}-response.dto.ts # Whitelist Output Response Schema
51
+ └── types/
52
+ └── {domain}/
53
+ ├── create-{entity}-input.type.ts # v.InferInput<typeof CreateEntityDto>
54
+ └── {entity}-response.type.ts # v.InferOutput<typeof EntityResponseDto>
55
+ ```
56
+
32
57
  ---
33
58
 
34
- ## 2. Standard Schema Example
59
+ ## 3. Bidirectional Schema Example
35
60
 
61
+ ### Input Schema (`src/dtos/users/create-user.dto.ts`):
36
62
  ```ts
37
63
  import * as v from "valibot";
38
64
 
39
- export const CreateUserSchema = v.object({
65
+ export const CreateUserDto = v.object({
40
66
  fullName: v.pipe(
41
67
  v.string("El nombre completo es requerido."),
42
68
  v.trim(),
@@ -45,22 +71,38 @@ export const CreateUserSchema = v.object({
45
71
  email: v.pipe(
46
72
  v.string("El correo electrónico es requerido."),
47
73
  v.trim(),
74
+ v.toLowerCase(),
48
75
  v.email("Por favor ingresa un correo electrónico válido.")
49
76
  ),
50
- age: v.pipe(
51
- v.number("La edad debe ser un número."),
52
- v.integer("La edad debe ser un número entero."),
53
- v.minValue(18, "Debes ser mayor de 18 años para registrarte.")
77
+ });
78
+ ```
79
+
80
+ ### Output Whitelist Schema (`src/dtos/users/user-response.dto.ts`):
81
+ ```ts
82
+ import * as v from "valibot";
83
+
84
+ export const UserResponseDto = v.object({
85
+ id: v.string(),
86
+ fullName: v.string(),
87
+ email: v.string(),
88
+ createdAt: v.pipe(
89
+ v.union([v.date(), v.string(), v.number()]),
90
+ v.transform((value) => new Date(value).toISOString())
54
91
  ),
55
92
  });
93
+ ```
94
+
95
+ ### Inferred Type Isolation (`src/types/users/create-user-input.type.ts`):
96
+ ```ts
97
+ import type * as v from "valibot";
98
+ import type { CreateUserDto } from "@/dtos/users/create-user.dto";
56
99
 
57
- export type CreateUserInput = v.InferInput<typeof CreateUserSchema>;
58
- export type CreateUserOutput = v.InferOutput<typeof CreateUserSchema>;
100
+ export type CreateUserInput = v.InferInput<typeof CreateUserDto>;
59
101
  ```
60
102
 
61
103
  ---
62
104
 
63
- ## 3. Safe Parsing Helper Pattern
105
+ ## 4. Safe Parsing Helper Pattern
64
106
 
65
107
  ```ts
66
108
  import * as v from "valibot";
@@ -95,8 +137,9 @@ export function validateSchema<TSchema extends v.BaseSchema<unknown, unknown, v.
95
137
 
96
138
  ---
97
139
 
98
- ## 4. Sibling Skills
140
+ ## 5. Sibling Skills
99
141
 
100
142
  - **`best-practices`**: Universal clean code and `<200` line limits.
101
143
  - **`ux-writing`**: Form validation copy and tone standards.
102
- - **`hono`**: Hono API route integration with `@hono/valibot-validator` and `sValidator`.
144
+ - **`hono`**: Hono API route integration with validation hooks and thin route wrappers.
145
+ - **`typescript-clean`**: Isolated type definitions and strict contracts.
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: workers-rpc-precision
3
+ description: Cloudflare Workers Native RPC (WorkerEntrypoint & Service<T>) and high-precision financial/date calculation standards in Python or TypeScript. TRIGGER when implementing Cloudflare Service Bindings, Workers RPC, decimal financial math (taxes, discounts, totals), billing cycles with timezones, or apps/py-worker.
4
+ ---
5
+
6
+ # Cloudflare Workers RPC & High-Precision Calculations
7
+
8
+ Architectural standard for zero-overhead, in-memory **Cloudflare Workers RPC** via `WorkerEntrypoint` and `Service<T>`, combined with high-precision financial calculations (`Decimal`) and timezone-aware dates.
9
+
10
+ ---
11
+
12
+ ## 1. Golden Rules (Non-negotiable)
13
+
14
+ 1. **Native Workers RPC (No `fetch()` or Fake HTTP URLs)**:
15
+ - **NEVER** use `fetch()`, fake URLs (`http://calculator-internal`), or HTTP headers to communicate between bound workers.
16
+ - Invoke RPC methods directly: `await env.CALCULATOR.calculate_order_totals(payload);`.
17
+
18
+ 2. **Clean Boundary Separation**:
19
+ - High-precision math and domain calculations live in the specialized worker (`apps/py-worker` or `apps/calculator-worker`).
20
+ - Business orchestration and API routing live in the primary backend (`apps/backend` or `apps/api`).
21
+
22
+ 3. **High-Precision Arithmetic (Zero Floating-Point for Money)**:
23
+ - Never use JavaScript `number` or Python `float` for currency, taxes, discounts, or subtotals.
24
+ - In Python, use `Decimal` from the standard library with explicit quantization (`ROUND_HALF_UP`).
25
+ - Safely extract data from Pyodide JS proxies using `to_py()` or JSON normalization.
26
+
27
+ 4. **Timezone-Aware Date Computations**:
28
+ - Compute billing cycles, deadlines, and fiscal periods using explicit timezones (`zoneinfo.ZoneInfo`) and ISO 8601 formatting.
29
+
30
+ 5. **End-to-End TypeScript RPC Contract**:
31
+ - Define exact RPC parameter and return types in `@/types/services/{service}.type.ts`.
32
+ - Bind `SERVICE_NAME: Service<YourRpcInterface>` in `@/types/context/env.type.ts`.
33
+
34
+ 6. **Caller Action Isolation**:
35
+ - Wrap RPC invocations inside dedicated, atomic Actions (`@/actions/{domain}/{name}.action.ts`).
36
+
37
+ 7. **Hard Size Limit: <200 Lines**:
38
+ - Keep entrypoints, pure arithmetic helpers, and domain handlers under 200 lines (target: 20-80 lines).
39
+
40
+ ---
41
+
42
+ ## 2. Directory Taxonomy
43
+
44
+ ```text
45
+ apps/py-worker/ (or specialized worker)
46
+ ├── wrangler.jsonc # flags: ["python_workers"] or Service Bindings
47
+ ├── requirements.txt # Dependencies
48
+ └── src/
49
+ ├── entry.py # WorkerEntrypoint exposing RPC methods (<60 lines)
50
+ ├── core/
51
+ │ ├── money.py # Pure Decimal math & rounding (<50 lines)
52
+ │ └── dates.py # Pure timezone & datetime logic (<50 lines)
53
+ └── domains/
54
+ └── finance/
55
+ └── calculate_totals.py # Domain handler (<70 lines)
56
+
57
+ apps/backend/src/
58
+ ├── types/services/
59
+ │ └── calculator-service.type.ts # TypeScript RPC interface (<50 lines)
60
+ └── actions/finance/
61
+ └── calculate-totals.action.ts # Action wrapping RPC call (<40 lines)
62
+ ```
63
+
64
+ ---
65
+
66
+ ## 3. End-to-End RPC Blueprint
67
+
68
+ ### Python Worker Entrypoint (`src/entry.py`):
69
+ ```python
70
+ from workers import WorkerEntrypoint
71
+ from domains.finance.calculate_totals import handle_calculate_totals
72
+
73
+ class CalculatorRpcService(WorkerEntrypoint):
74
+ async def calculate_order_totals(self, data: dict) -> dict:
75
+ return handle_calculate_totals(data)
76
+ ```
77
+
78
+ ### TypeScript RPC Contract (`@/types/services/calculator-service.type.ts`):
79
+ ```ts
80
+ export interface OrderItemInput {
81
+ id?: string;
82
+ unitPrice: string | number;
83
+ quantity: number;
84
+ discountPct?: string | number;
85
+ }
86
+
87
+ export interface CalculateTotalsPayload {
88
+ items: OrderItemInput[];
89
+ taxRatePct: string | number;
90
+ }
91
+
92
+ export interface CalculateTotalsResult {
93
+ items: Array<{ id?: string; itemTotal: string }>;
94
+ subtotal: string;
95
+ tax: string;
96
+ total: string;
97
+ }
98
+
99
+ export interface CalculatorRpcService {
100
+ calculate_order_totals(data: CalculateTotalsPayload): Promise<CalculateTotalsResult>;
101
+ }
102
+ ```
103
+
104
+ ### Caller Action in TypeScript (`@/actions/finance/calculate-totals.action.ts`):
105
+ ```ts
106
+ import type { Service } from "@cloudflare/workers-types";
107
+ import type {
108
+ CalculatorRpcService,
109
+ CalculateTotalsPayload,
110
+ CalculateTotalsResult,
111
+ } from "@/types/services/calculator-service.type";
112
+
113
+ export interface CalculateTotalsActionInput {
114
+ calculator: Service<CalculatorRpcService>;
115
+ payload: CalculateTotalsPayload;
116
+ }
117
+
118
+ export async function calculateTotalsAction(
119
+ input: CalculateTotalsActionInput
120
+ ): Promise<CalculateTotalsResult> {
121
+ return await input.calculator.calculate_order_totals(input.payload);
122
+ }
123
+ ```
124
+
125
+ ---
126
+
127
+ ## 4. Deep References
128
+
129
+ - `./references/python-worker-rpc-examples.md`: Complete implementations of `money.py`, Pyodide proxy conversion, and billing cycle handlers.
@@ -0,0 +1,98 @@
1
+ # Implementation Reference: Python Workers RPC & Precision Math
2
+
3
+ Complete helpers and handlers for high-precision arithmetic, JS proxy conversion, and timezone calculations on Cloudflare Workers.
4
+
5
+ ---
6
+
7
+ ## 1. Pure Decimal Arithmetic (`src/core/money.py`)
8
+
9
+ ```python
10
+ from decimal import Decimal, ROUND_HALF_UP
11
+
12
+ def to_decimal(val) -> Decimal:
13
+ return Decimal(str(val))
14
+
15
+ def round_currency(amount: Decimal, decimals: int = 2) -> Decimal:
16
+ exponent = Decimal("10") ** -decimals
17
+ return amount.quantize(exponent, rounding=ROUND_HALF_UP)
18
+
19
+ def compute_item_total(unit_price: Decimal, quantity: int, discount_pct: Decimal = Decimal(0)) -> Decimal:
20
+ subtotal = unit_price * quantity
21
+ discount = subtotal * (discount_pct / Decimal(100))
22
+ return round_currency(subtotal - discount)
23
+
24
+ def compute_tax(amount: Decimal, tax_rate_pct: Decimal) -> Decimal:
25
+ return round_currency(amount * (tax_rate_pct / Decimal(100)))
26
+ ```
27
+
28
+ ---
29
+
30
+ ## 2. Safe Pyodide JS Proxy Handler (`src/domains/finance/calculate_totals.py`)
31
+
32
+ ```python
33
+ import json
34
+ from decimal import Decimal
35
+ from typing import Any
36
+ from core.money import to_decimal, compute_item_total, compute_tax, round_currency
37
+
38
+ try:
39
+ from js import JSON # type: ignore
40
+ except ImportError:
41
+ JSON = None
42
+
43
+ def handle_calculate_totals(raw_data: Any) -> Any:
44
+ # Safely convert Pyodide JS Proxy or dict
45
+ if hasattr(raw_data, "to_py"):
46
+ data = raw_data.to_py()
47
+ elif isinstance(raw_data, dict):
48
+ data = raw_data
49
+ elif JSON is not None:
50
+ data = json.loads(JSON.stringify(raw_data))
51
+ else:
52
+ data = dict(raw_data)
53
+
54
+ items = data.get("items", [])
55
+ tax_rate = to_decimal(data.get("taxRatePct", 0))
56
+ subtotal = Decimal("0")
57
+ processed_items = []
58
+
59
+ for item in items:
60
+ unit_price = to_decimal(item.get("unitPrice", 0))
61
+ quantity = int(item.get("quantity", 1))
62
+ discount_pct = to_decimal(item.get("discountPct", 0))
63
+ item_total = compute_item_total(unit_price, quantity, discount_pct)
64
+ subtotal += item_total
65
+ processed_items.append({"id": item.get("id"), "itemTotal": str(item_total)})
66
+
67
+ tax = compute_tax(subtotal, tax_rate)
68
+ total = round_currency(subtotal + tax)
69
+
70
+ result = {
71
+ "items": processed_items,
72
+ "subtotal": str(subtotal),
73
+ "tax": str(tax),
74
+ "total": str(total),
75
+ }
76
+
77
+ if JSON is not None:
78
+ return JSON.parse(json.dumps(result))
79
+ return result
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 3. Wrangler Service Binding Configuration
85
+
86
+ ```jsonc
87
+ // apps/backend/wrangler.jsonc
88
+ {
89
+ "name": "my-backend",
90
+ "services": [
91
+ {
92
+ "binding": "CALCULATOR",
93
+ "service": "my-py-worker",
94
+ "entrypoint": "CalculatorRpcService"
95
+ }
96
+ ]
97
+ }
98
+ ```