@3lineas/grooveskills 1.7.1 → 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.
@@ -0,0 +1,69 @@
1
+ # Implementation Reference: Backend Testing Pyramid & In-Memory D1 Setup
2
+
3
+ A complete guide for orchestrating Vitest test suites across the 4-tier testing pyramid using in-memory SQLite / Cloudflare D1 emulators.
4
+
5
+ ---
6
+
7
+ ## 1. Global Setup with In-Memory Database (`setup.ts`)
8
+
9
+ Instead of recreating database tables inside every test file, initialize migrations once on an in-memory database:
10
+
11
+ ```ts
12
+ import { beforeAll, afterAll } from "vitest";
13
+ import Database from "better-sqlite3";
14
+ import { drizzle } from "drizzle-orm/better-sqlite3";
15
+ import { migrate } from "drizzle-orm/better-sqlite3/migrator";
16
+ import * as schema from "@/db/schema";
17
+
18
+ const sqliteConnection = new Database(":memory:");
19
+ export const testDb = drizzle(sqliteConnection, { schema });
20
+
21
+ beforeAll(async () => {
22
+ // Apply schema migrations automatically before suite execution
23
+ migrate(testDb, { migrationsFolder: "./drizzle" });
24
+ });
25
+
26
+ afterAll(() => {
27
+ sqliteConnection.close();
28
+ });
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 2. Testing Transactions & Rollback (Tier 3)
34
+
35
+ ```ts
36
+ import { describe, it, expect } from "vitest";
37
+ import { withD1Transaction } from "@/common/utils/d1-transaction";
38
+ import { testDb } from "@/backend/setup";
39
+ import { usersTable } from "@/db/schema";
40
+
41
+ describe("D1 Atomic Batch Transactions", () => {
42
+ it("should rollback all mutations if an error occurs mid-transaction", async () => {
43
+ const executeFailingTransaction = async () => {
44
+ await withD1Transaction(testDb as any, async (transaction) => {
45
+ transaction.stage(
46
+ testDb.insert(usersTable).values({ id: "user_1", email: "first@example.com" }) as any
47
+ );
48
+
49
+ throw new Error("Simulated failure mid-transaction");
50
+ });
51
+ };
52
+
53
+ await expect(executeFailingTransaction()).rejects.toThrow("Simulated failure mid-transaction");
54
+
55
+ const users = await testDb.select().from(usersTable);
56
+ expect(users).toHaveLength(0);
57
+ });
58
+ });
59
+ ```
60
+
61
+ ---
62
+
63
+ ## 3. Best Practices Checklist
64
+
65
+ - [ ] Run migrations on in-memory SQLite rather than hitting live cloud databases.
66
+ - [ ] Reset mutable database state between tests using transaction rollbacks or truncation.
67
+ - [ ] Validate both happy path and rejection paths on every DTO schema.
68
+ - [ ] Ensure route tests never assert raw implementation details, only HTTP status codes and `ActionResponse<T>` envelopes.
69
+ - [ ] Keep every test file strictly below 200 lines.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: typescript-clean
3
- description: Advanced TypeScript 5.5+ standards, strict tsconfig rules, discriminated unions, satisfies operator, const type parameters, unknown over any, custom type guards, and elimination of unsafe type assertions (as / any). TRIGGER when writing or modifying TypeScript files (.ts, .tsx), type definitions, tsconfig.json, or when handling complex polymorphic domain models.
3
+ description: Advanced TypeScript 5.5+ standards, strict tsconfig rules, discriminated unions, satisfies operator, const type parameters, unknown over any, atomic type isolation in @/types/, and elimination of unsafe type assertions (as / any). TRIGGER when writing or modifying TypeScript files (.ts, .tsx), type definitions, tsconfig.json, or when handling complex polymorphic domain models.
4
4
  ---
5
5
 
6
6
  # TypeScript Clean Code Standards (TS 5.5+)
7
7
 
8
- Guidelines for writing safe, expressive, and strictly typed TypeScript with zero unsafe casts, exhaustive pattern matching, and modern language features.
8
+ Guidelines for writing safe, expressive, strictly typed TypeScript with zero unsafe casts, exhaustive pattern matching, and atomic domain type isolation.
9
9
 
10
10
  ---
11
11
 
@@ -15,31 +15,57 @@ Guidelines for writing safe, expressive, and strictly typed TypeScript with zero
15
15
  - `any` disables the compiler and hides runtime bugs. Use `unknown` with narrowers or generics.
16
16
  - Non-null assertion operator `!` assumes runtime state without proof. Use optional chaining `?.` or explicit guard clauses.
17
17
 
18
- 2. **Discriminated Unions for Domain States**:
18
+ 2. **Strict Type Isolation (`@/types/{domain}/`)**:
19
+ - In modular domain architectures, **NEVER** write `interface` or `type` definitions inside DTOs, Actions, Routes, or Utility helpers.
20
+ - Every contract has its own dedicated file: `@/types/{domain}/{name}.type.ts`.
21
+ - Organize into domains: `common/`, `context/`, `db/`, `pipeline/`, `services/`, and `{domain}/`.
22
+
23
+ 3. **Discriminated Unions for Domain States**:
19
24
  - Represent valid combinations of state using tagged unions (`type = "idle" | "loading" | "success" | "error"`).
20
25
  - Never use "optional soup" where every field is `x?: string`.
21
26
 
22
- 3. **Exhaustive Type Checking with `never`**:
27
+ 4. **Exhaustive Type Checking with `never`**:
23
28
  - Every `switch` or conditional on a union must have an `assertNever(value)` exhaustive check in default branch.
24
29
 
25
- 4. **Favor `satisfies` Over Type Annotations on Literals**:
30
+ 5. **Favor `satisfies` Over Type Annotations on Literals**:
26
31
  - `satisfies` checks that a value adheres to a contract without widening or discarding specific literal types.
27
32
 
28
- 5. **`const` Type Parameters (TS 5.0+)**:
33
+ 6. **`const` Type Parameters (TS 5.0+)**:
29
34
  - Use `<const T>` in generic helper functions to preserve literal types without requiring `as const` at call site.
30
35
 
31
- 6. **Custom Type Guards & Inferred Type Predicates (TS 5.5+)**:
36
+ 7. **Custom Type Guards & Inferred Type Predicates (TS 5.5+)**:
32
37
  - Use type predicates (`x is TargetType`) or rely on TS 5.5 automatic type narrowing for array filters.
33
38
 
34
- 7. **Avoid Unsafe `as` Type Assertions**:
39
+ 8. **Avoid Unsafe `as` Type Assertions**:
35
40
  - Never use `value as SomeType` to bypass the type checker. Validate with Valibot schema or custom type guard first.
36
41
 
42
+ 9. **Hard Size Limit of <200 Lines**:
43
+ - Keep type definition files atomic and concise (target: 10-50 lines).
44
+
37
45
  ---
38
46
 
39
- ## 2. Key Patterns
47
+ ## 2. Directory Structure for Type Isolation
48
+
49
+ ```text
50
+ src/types/
51
+ ├── common/ # Global shared contracts
52
+ │ └── action-response.type.ts # interface ActionResponse<T>
53
+ ├── context/ # Infrastructure & runtime execution context
54
+ │ ├── env.type.ts # interface Env (Bindings / Secrets)
55
+ │ └── app-context.type.ts # Context typing (Bindings, Variables)
56
+ ├── db/ # Database client and ORM entities
57
+ │ ├── database-client.type.ts # Database client handle
58
+ │ └── {entity}.type.ts # Select / Insert schema types
59
+ └── {domain}/ # Domain-specific DTO & action contracts
60
+ ├── create-{entity}-input.type.ts # Inferred from input DTO
61
+ └── {entity}-response.type.ts # Inferred from output DTO
62
+ ```
40
63
 
41
- ### A. Discriminated Unions & Exhaustiveness
64
+ ---
42
65
 
66
+ ## 3. Key Patterns
67
+
68
+ ### A. Discriminated Unions & Exhaustiveness
43
69
  ```ts
44
70
  export type RequestState<T> =
45
71
  | { status: "idle" }
@@ -68,33 +94,27 @@ export function renderStatus<T>(state: RequestState<T>): string {
68
94
  ```
69
95
 
70
96
  ### B. `satisfies` vs Widening Annotations
71
-
72
97
  ```ts
73
98
  type RouteConfig = Record<string, { path: string; isProtected: boolean }>;
74
99
 
75
- // ✅ Preserves exact key literals and structure
76
100
  export const APP_ROUTES = {
77
101
  HOME: { path: "/", isProtected: false },
78
102
  DASHBOARD: { path: "/dashboard", isProtected: true },
79
103
  } satisfies RouteConfig;
80
-
81
- // APP_ROUTES.HOME.path is typed as "/" not string!
82
104
  ```
83
105
 
84
106
  ### C. Const Generic Parameters
85
-
86
107
  ```ts
87
108
  export function createEnumSet<const T extends readonly string[]>(values: T): ReadonlySet<T[number]> {
88
109
  return new Set(values);
89
110
  }
90
111
 
91
- // Inferred as ReadonlySet<"admin" | "editor" | "viewer">
92
112
  export const ROLES = createEnumSet(["admin", "editor", "viewer"]);
93
113
  ```
94
114
 
95
115
  ---
96
116
 
97
- ## 3. Strict TSConfig Expectations
117
+ ## 4. Strict TSConfig Expectations
98
118
 
99
119
  Always enable:
100
120
  - `"strict": true`
@@ -104,8 +124,8 @@ Always enable:
104
124
 
105
125
  ---
106
126
 
107
- ## 4. Sibling Skills
127
+ ## 5. Sibling Skills
108
128
 
109
- - **`best-practices`**: Universal clean code rules and 200-line limit.
129
+ - **`best-practices`**: Universal clean code rules, descriptive naming, and 200-line limit.
110
130
  - **`valibot-validation`**: Runtime validation inferred to static TS types.
111
- - Reference guide: `references/js-ts-antipatterns.md`.
131
+ - Reference guide: `./references/js-ts-antipatterns.md`.
@@ -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
+ ```