diff --git a/packages/app/eslint.config.mts b/packages/app/eslint.config.mts index bf8875e..57ed136 100644 --- a/packages/app/eslint.config.mts +++ b/packages/app/eslint.config.mts @@ -4,7 +4,6 @@ import eslint from '@eslint/js'; import { defineConfig } from 'eslint/config'; import tseslint from 'typescript-eslint'; import vitest from "@vitest/eslint-plugin"; -import suggestMembers from "@prover-coder-ai/eslint-plugin-suggest-members"; import sonarjs from "eslint-plugin-sonarjs"; import unicorn from "eslint-plugin-unicorn"; import * as effectEslint from "@effect/eslint-plugin"; @@ -37,7 +36,6 @@ export default defineConfig( eslint.configs.recommended, tseslint.configs.strictTypeChecked, effectEslint.configs.dprint, - suggestMembers.configs.recommended, eslintCommentsConfigs.recommended, { name: "analyzers", diff --git a/packages/app/examples/strict-error-handling.ts b/packages/app/examples/strict-error-handling.ts index 12321e1..06b2419 100644 --- a/packages/app/examples/strict-error-handling.ts +++ b/packages/app/examples/strict-error-handling.ts @@ -1,203 +1,55 @@ // CHANGE: Strict example demonstrating forced E=never error handling -// WHY: Prove that after catchTags with Match.exhaustive, the error channel becomes 'never' +// WHY: Prove that after catchTags with Match.exhaustive, the error channel becomes never // QUOTE(ТЗ): "Приёмка по смыслу: после catchTags(...) тип ошибки становится never" // REF: PR#3 blocking review from skulidropek // SOURCE: n/a // PURITY: SHELL -// EFFECT: Effect - all errors handled +// EFFECT: Effect - all errors handled -import * as FetchHttpClient from "@effect/platform/FetchHttpClient" -import type * as HttpClient from "@effect/platform/HttpClient" -import { Cause, Console, Effect, Match } from "effect" -import "../src/generated/dispatchers-by-path.js" -import { type ClientOptions, createClient } from "../src/shell/api-client/create-client.js" +import { Console, Effect, Match } from "effect" + +import { type ClientOptions, createClientEffect } from "../src/index.js" import type { Paths } from "../tests/fixtures/petstore.openapi.js" -/** - * Client configuration - */ const clientOptions: ClientOptions = { baseUrl: "https://petstore.example.com", credentials: "include" } -// CHANGE: Use default dispatcher registry (registered by generated module) -// WHY: Call createClient(options) without passing dispatcher map -// QUOTE(ТЗ): "const apiClient = createClient(clientOptions)" -// REF: user-msg-4 +// CHANGE: Use createClientEffect with openapi-fetch-compatible inputs +// WHY: Output channels are inferred from createClientEffect() without per-call schema +// QUOTE(ТЗ): "input должен быть 1 в 1" +// REF: user-msg-openapi-effect-input-compat // SOURCE: n/a -// FORMAT THEOREM: ∀ op ∈ Operations: createClient(options) uses registered dispatchers +// FORMAT THEOREM: ∀ path, method: responses(Paths[path][method]) -> Effect // PURITY: SHELL // EFFECT: none -// INVARIANT: default dispatchers registered before client creation +// INVARIANT: Path and method select the OpenAPI operation that determines success and failure channels // COMPLEXITY: O(1) -const apiClient = createClient(clientOptions) - -// ============================================================================= -// STRICT EXAMPLE 1: getPet - handles 404, 500 + all boundary errors -// ============================================================================= - -/** - * CRITICAL: This program has E=never - all errors are explicitly handled! - * - * The reviewer requires: - * 1. Only Match.exhaustive (no Match.orElse) - * 2. All _tag variants handled via catchTags - * 3. After catchTags, type becomes Effect - * - * Schema: getPet has responses 200 (success), 404 (error), 500 (error) - * Error channel: HttpError<404 | 500> | BoundaryError - * - * @invariant After catchTags, E = never - * @effect Effect - */ -export const getPetStrictProgram: Effect.Effect = Effect.gen(function*() { - yield* Console.log("=== getPet: Strict Error Handling ===") - - // Execute request - yields only on 200 - yield* apiClient.GET("/pets/{petId}", { params: { petId: "123" } }).pipe( - Effect.tap((result) => Console.log(`Got pet: ${result.body.name}`)) - ) -}).pipe( - // Handle HttpError with EXHAUSTIVE matching (no orElse!) - Effect.catchTag("HttpError", (error) => - Match.value(error.status).pipe( - Match.when(404, () => Console.log(`Not found: ${JSON.stringify(error.body)}`)), - Match.when(500, () => Console.log(`Server error: ${JSON.stringify(error.body)}`)), - // CRITICAL: Match.exhaustive - forces handling ALL schema statuses - // If a new status (e.g., 401) is added to schema, this will fail typecheck - Match.exhaustive - )), - // Handle ALL boundary errors - Effect.catchTag("TransportError", (e) => Console.log(`Transport error: ${e.error.message}`)), - Effect.catchTag("UnexpectedStatus", (e) => Console.log(`Unexpected status: ${e.status}`)), - Effect.catchTag("UnexpectedContentType", (e) => Console.log(`Unexpected content-type: ${e.actual ?? "unknown"}`)), - Effect.catchTag("ParseError", (e) => Console.log(`Parse error: ${e.error.message}`)), - Effect.catchTag("DecodeError", (e) => Console.log(`Decode error: ${e.error.message}`)) -) - -// ============================================================================= -// STRICT EXAMPLE 2: createPet - handles 400, 500 + all boundary errors -// ============================================================================= - -/** - * createPet strict handler - * - * Schema: createPet has responses 201 (success), 400 (error), 500 (error) - * Error channel: HttpError<400 | 500> | BoundaryError - * - * @invariant After catchTags, E = never - * @effect Effect - */ -export const createPetStrictProgram: Effect.Effect = Effect.gen(function*() { - yield* Console.log("=== createPet: Strict Error Handling ===") - - yield* apiClient.POST( - "/pets", - { - // Body can be typed object - client will auto-stringify and set Content-Type - body: { name: "Fluffy", tag: "cat" } - } - ).pipe( - Effect.tap((result) => Console.log(`Created pet: ${result.body.id}`)) - ) -}).pipe( - // Handle HttpError with EXHAUSTIVE matching - Effect.catchTag("HttpError", (error) => - Match.value(error.status).pipe( - Match.when(400, () => Console.log(`Validation error: ${JSON.stringify(error.body)}`)), - Match.when(500, () => Console.log(`Server error: ${JSON.stringify(error.body)}`)), - // Match.exhaustive forces handling 400 AND 500 - Match.exhaustive - )), - // Handle ALL boundary errors - Effect.catchTag("TransportError", (e) => Console.log(`Transport error: ${e.error.message}`)), - Effect.catchTag("UnexpectedStatus", (e) => Console.log(`Unexpected status: ${e.status}`)), - Effect.catchTag("UnexpectedContentType", (e) => Console.log(`Unexpected content-type: ${e.actual ?? "unknown"}`)), - Effect.catchTag("ParseError", (e) => Console.log(`Parse error: ${e.error.message}`)), - Effect.catchTag("DecodeError", (e) => Console.log(`Decode error: ${e.error.message}`)) -) - -// ============================================================================= -// STRICT EXAMPLE 3: listPets - handles 500 + all boundary errors -// ============================================================================= +const apiClient = createClientEffect(clientOptions) -/** - * listPets strict handler - * - * Schema: listPets has responses 200 (success), 500 (error) - * Error channel: HttpError<500> | BoundaryError - * - * @invariant After catchTags, E = never - * @effect Effect - */ -export const listPetsStrictProgram: Effect.Effect = Effect.gen(function*() { - yield* Console.log("=== listPets: Strict Error Handling ===") - - yield* apiClient.GET("/pets", { query: { limit: 10 } }).pipe( - Effect.tap((result) => Console.log(`Got ${result.body.length} pets`)) - ) - - const pets = yield* apiClient.GET("/pets", { query: { limit: 10 } }) - - yield* Console.log(`Got ${pets.body.length} pets`) +const listPetsProgram = apiClient.GET("/pets", { + params: { query: { limit: 10 } } }).pipe( - // Handle HttpError with EXHAUSTIVE matching - Effect.catchTag("HttpError", (error) => - Match.value(error.status).pipe( - Match.when(500, () => Console.log(`Server error: ${JSON.stringify(error.body)}`)), - // Match.exhaustive - only 500 needs handling for listPets + Effect.flatMap((success) => + Match.value(success).pipe( + Match.when({ status: 200 }, ({ body }) => Console.log(`Got ${body.length} pets`)), Match.exhaustive - )), - // Handle ALL boundary errors - Effect.catchTag("TransportError", (e) => Console.log(`Transport error: ${e.error.message}`)), - Effect.catchTag("UnexpectedStatus", (e) => Console.log(`Unexpected status: ${e.status}`)), - Effect.catchTag("UnexpectedContentType", (e) => Console.log(`Unexpected content-type: ${e.actual ?? "unknown"}`)), - Effect.catchTag("ParseError", (e) => Console.log(`Parse error: ${e.error.message}`)), - Effect.catchTag("DecodeError", (e) => Console.log(`Decode error: ${e.error.message}`)) -) - -// ============================================================================= -// MAIN: Run all strict programs -// ============================================================================= - -/** - * Main program combines all strict examples - * Type annotation proves E=never: Effect - */ -const mainProgram: Effect.Effect = Effect.gen(function*() { - yield* Console.log("========================================") - yield* Console.log(" Strict Error Handling Examples") - yield* Console.log(" (All have E=never)") - yield* Console.log("========================================\n") - - // All these programs have E=never - errors fully handled - yield* getPetStrictProgram - yield* Console.log("") - - yield* createPetStrictProgram - yield* Console.log("") - - yield* listPetsStrictProgram - - yield* Console.log("\n========================================") - yield* Console.log(" All errors handled - E=never verified!") - yield* Console.log("========================================") -}) - -// CHANGE: Remove async/await entrypoint and handle defects in Effect -// WHY: Lint rules forbid async/await and floating promises; defects are handled in Effect channel -// QUOTE(ТЗ): "Запрещён async/await — используй Effect.gen / Effect.tryPromise." -// REF: user-msg-2 -// SOURCE: n/a -// FORMAT THEOREM: For all exits, mainProgram E=never implies failure(exit) -> defect(exit) -// PURITY: SHELL -// EFFECT: Effect -> Promise via Effect.runPromise -// INVARIANT: Typed error channel remains never -// COMPLEXITY: O(1) -const program = mainProgram.pipe( - Effect.provide(FetchHttpClient.layer), - Effect.catchAllCause((cause) => Console.error(`Unexpected defect: ${Cause.pretty(cause)}`)) + ) + ), + Effect.catchTags({ + HttpError: (error) => + Match.value(error.status).pipe( + Match.when(500, () => Console.log(`Server error: ${error.body.message}`)), + Match.exhaustive + ), + TransportError: ({ error }) => Console.log(`Transport error: ${error.message}`), + UnexpectedStatus: ({ body, status }) => Console.log(`Unexpected status ${status}: ${body}`), + UnexpectedContentType: ({ actual, expected }) => + Console.log(`Unexpected content type ${actual ?? "unknown"}; expected ${expected.join(", ")}`), + ParseError: ({ error }) => Console.log(`Parse error: ${error.message}`), + DecodeError: ({ error }) => Console.log(`Decode error: ${error.message}`) + }) ) -void Effect.runPromise(program) +export const strictErrorHandlingProgram: Effect.Effect = listPetsProgram diff --git a/packages/app/examples/test-create-client.ts b/packages/app/examples/test-create-client.ts deleted file mode 100644 index 3980421..0000000 --- a/packages/app/examples/test-create-client.ts +++ /dev/null @@ -1,267 +0,0 @@ -// CHANGE: Example script demonstrating Effect-native error handling with createClient -// WHY: Show how to handle HTTP errors (404, 500) via Effect error channel -// QUOTE(TZ): "Мы не заставляем обрабатывать потенциальные исключения... Должно быть типо результат который принимается и потециальные исключения которые надо обработать" -// REF: PR#3 comment from skulidropek about Effect representation -// SOURCE: n/a -// PURITY: SHELL -// EFFECT: Demonstrates Effect-based API calls with forced error handling - -import * as FetchHttpClient from "@effect/platform/FetchHttpClient" -import { Console, Effect, Exit, Match } from "effect" -import { - createClient, - type ClientOptions -} from "../src/shell/api-client/create-client.js" -import "../src/generated/dispatchers-by-path.js" -import type { Paths } from "../tests/fixtures/petstore.openapi.js" -// Types are automatically inferred - no need to import them explicitly - -/** - * Example: Create API client with simplified API - * - * This demonstrates the ergonomic createClient API that matches - * the interface requested by the reviewer. - */ -const clientOptions: ClientOptions = { - baseUrl: "https://petstore.example.com", - credentials: "include" -} - -// CHANGE: Use default dispatcher registry (registered by generated module) -// WHY: Call createClient(options) without passing dispatcher map -// QUOTE(ТЗ): "const apiClient = createClient(clientOptions)" -// REF: user-msg-4 -// SOURCE: n/a -// FORMAT THEOREM: ∀ op ∈ Operations: createClient(options) uses registered dispatchers -// PURITY: SHELL -// EFFECT: none -// INVARIANT: default dispatchers registered before client creation -// COMPLEXITY: O(1) -const apiClient = createClient(clientOptions) - -/** - * Example program: List all pets with Effect-native error handling - * - * NEW DESIGN: - * - Success channel (yield*): Only 2xx responses - * - Error channel (catchTag/catchAll): HTTP errors (500) + boundary errors - * - * This FORCES developers to handle HTTP errors explicitly! - * - * @pure false - performs HTTP request - */ -const listAllPetsExample = Effect.gen(function*() { - yield* Console.log("=== Example 1: List all pets ===") - - // Execute request - type is automatically inferred from dispatcherlistPets - // Now: success = 200 only, error = 500 | BoundaryError - const result = yield* apiClient.GET( - "/pets", - { - query: { limit: 10 } - } - ) - - // Success! We only get here if status was 200 - // No need to check status - TypeScript knows it's 200 - const pets = result.body - yield* Console.log(`Success: Got ${pets.length} pets`) - if (pets.length > 0) { - yield* Console.log(` First pet: ${JSON.stringify(pets[0], null, 2)}`) - } -}).pipe( - // HTTP errors (500) now require explicit handling! - Effect.catchTag("HttpError", (error) => - Console.log(`Server error (500): ${JSON.stringify(error.body)}`)), - // Boundary errors are also in error channel - Effect.catchTag("TransportError", (error) => - Console.log(`Transport error: ${error.error.message}`)), - Effect.catchTag("UnexpectedStatus", (error) => - Console.log(`Unexpected status: ${error.status}`)) -) - -/** - * Example program: Get specific pet - * - * Demonstrates handling multiple HTTP error statuses (404, 500). - * Uses Match.exhaustive to force handling ALL schema-defined statuses. - * - * @pure false - performs HTTP request - */ -const getPetExample = Effect.gen(function*() { - yield* Console.log("\n=== Example 2: Get specific pet ===") - - // Type is inferred from dispatchergetPet - // Success = 200, Error = 404 | 500 | BoundaryError - const result = yield* apiClient.GET( - "/pets/{petId}", - { - params: { petId: "123" } - } - ) - - // Success! Status is guaranteed to be 200 - yield* Console.log(`Success: Got pet "${result.body.name}"`) - yield* Console.log(` Tag: ${result.body.tag ?? "none"}`) -}).pipe( - // Handle HTTP errors using Match.exhaustive - forces handling ALL schema statuses - // CRITICAL: Match.exhaustive, not Match.orElse! - Effect.catchTag("HttpError", (error) => - Match.value(error.status).pipe( - Match.when(404, () => Console.log(`Not found: ${JSON.stringify(error.body)}`)), - Match.when(500, () => Console.log(`Server error: ${JSON.stringify(error.body)}`)), - Match.exhaustive // Forces handling all 404 | 500 - no escape hatch - )), - Effect.catchTag("TransportError", (error) => - Console.log(`Transport error: ${error.error.message}`)) -) - -/** - * Example program: Create new pet - * - * Demonstrates handling validation errors (400). - * Uses Match.exhaustive to force handling ALL schema-defined statuses. - * - * @pure false - performs HTTP request - */ -const createPetExample = Effect.gen(function*() { - yield* Console.log("\n=== Example 3: Create new pet ===") - - const newPet = { - name: "Fluffy", - tag: "cat" - } - - // Type is inferred from dispatchercreatePet - // Success = 201, Error = 400 | 500 | BoundaryError - const result = yield* apiClient.POST( - "/pets", - { - // Typed body - client will auto-stringify and set Content-Type - body: newPet - } - ) - - // Success! Status is guaranteed to be 201 - yield* Console.log(`Success: Created pet with ID ${result.body.id}`) - yield* Console.log(` Name: ${result.body.name}`) -}).pipe( - // Handle HTTP errors - FORCED by TypeScript! - // CRITICAL: Match.exhaustive, not Match.orElse! - Effect.catchTag("HttpError", (error) => - Match.value(error.status).pipe( - Match.when(400, () => Console.log(`Validation error: ${JSON.stringify(error.body)}`)), - Match.when(500, () => Console.log(`Server error: ${JSON.stringify(error.body)}`)), - Match.exhaustive // Forces handling all 400 | 500 - no escape hatch - )), - Effect.catchTag("TransportError", (error) => - Console.log(`Transport error: ${error.error.message}`)) -) - -/** - * Example program: Using Effect.either for conditional error handling - * - * Demonstrates how to access both success and error in one place. - * - * @pure false - performs HTTP request - */ -const eitherExample = Effect.gen(function*() { - yield* Console.log("\n=== Example 4: Using Effect.either ===") - - const result = yield* Effect.either( - apiClient.GET("/pets/{petId}", { - params: { petId: "999" } // Non-existent pet - }) - ) - - if (result._tag === "Right") { - // Success - got the pet - yield* Console.log(`Found pet: ${result.right.body.name}`) - } else { - // Error - check the type - const error = result.left - if ("_tag" in error) { - if (error._tag === "HttpError") { - // HTTP error from schema (404 or 500) - yield* Console.log(`HTTP error ${error.status}: ${JSON.stringify(error.body)}`) - } else { - // Boundary error (TransportError, UnexpectedStatus, etc.) - yield* Console.log(`Boundary error: ${error._tag}`) - } - } - } -}) - -/** - * Main program - runs all examples - * - * @pure false - performs HTTP requests - */ -const mainProgram = Effect.gen(function*() { - yield* Console.log("========================================") - yield* Console.log(" OpenAPI Effect Client - Examples") - yield* Console.log(" Effect-Native Error Handling") - yield* Console.log("========================================\n") - - yield* Console.log("NEW DESIGN:") - yield* Console.log(" - Success channel: 2xx responses only") - yield* Console.log(" - Error channel: HTTP errors (4xx, 5xx) + boundary errors") - yield* Console.log(" - Developers MUST handle HTTP errors explicitly!\n") - - yield* Console.log("Example code:") - yield* Console.log(' const result = yield* client.GET("/path", { params })') - yield* Console.log(" // result is 200 - no need to check status!") - yield* Console.log("").pipe(Effect.flatMap(() => - Console.log(" // HTTP errors handled via Effect.catchTag or Effect.match\n") - )) - - // Note: These examples will fail with transport errors since - // we're not connecting to a real server. This is intentional - // to demonstrate error handling. - - yield* listAllPetsExample.pipe( - Effect.catchAll((error) => - Console.log(`Unhandled error in listAllPets: ${JSON.stringify(error)}`)) - ) - - yield* getPetExample.pipe( - Effect.catchAll((error) => - Console.log(`Unhandled error in getPet: ${JSON.stringify(error)}`)) - ) - - yield* createPetExample.pipe( - Effect.catchAll((error) => - Console.log(`Unhandled error in createPet: ${JSON.stringify(error)}`)) - ) - - yield* eitherExample.pipe( - Effect.catchAll((error) => - Console.log(`Unhandled error in either example: ${JSON.stringify(error)}`)) - ) - - yield* Console.log("\nAll examples completed!") - yield* Console.log("\nKey benefits of Effect-native error handling:") - yield* Console.log(" - HTTP errors (404, 500) FORCE explicit handling") - yield* Console.log(" - No accidental ignoring of error responses") - yield* Console.log(" - Type-safe discrimination via _tag and status") - yield* Console.log(" - Exhaustive pattern matching with Match.exhaustive") -}) - -/** - * Execute the program with FetchHttpClient layer - */ -const program = mainProgram.pipe( - Effect.provide(FetchHttpClient.layer) -) - -/** - * Run the program and handle errors - */ -const main = async () => { - const exit = await Effect.runPromiseExit(program) - if (Exit.isFailure(exit)) { - console.error("Unexpected error:", exit.cause) - } -} - -main() diff --git a/packages/app/package.json b/packages/app/package.json index 6d63acf..fb19c26 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,7 +1,7 @@ { "name": "@prover-coder-ai/openapi-effect", "version": "1.0.23", - "description": "Drop-in replacement for openapi-fetch with an opt-in Effect API", + "description": "openapi-fetch-compatible client that returns Effect values", "type": "module", "main": "dist/index.js", "exports": { @@ -25,63 +25,45 @@ "lint:tests": "npx @ton-ai-core/vibecode-linter tests/", "lint:effect": "npx eslint --config eslint.effect-ts-check.config.mjs .", "lint:types": "./scripts/lint-types.sh", - "check": "pnpm run typecheck", - "prestart": "pnpm run build", - "start": "node dist/main.js", - "test": "pnpm run lint:tests && vitest run", - "typecheck": "tsc --noEmit", - "gen:strict-api": "npx ts-node --esm scripts/gen-strict-api.ts" + "check": "tsc --noEmit", + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "repository": { "type": "git", - "url": "git+https://github.com/ProverCoderAI/effect-template.git" + "url": "git+https://github.com/ProverCoderAI/openapi-effect.git" }, "keywords": [ "effect", "typescript", - "vite", - "console" + "openapi", + "openapi-fetch" ], "author": "", "license": "ISC", "bugs": { - "url": "https://github.com/ProverCoderAI/effect-template/issues" + "url": "https://github.com/ProverCoderAI/openapi-effect/issues" }, - "homepage": "https://github.com/ProverCoderAI/effect-template#readme", + "homepage": "https://github.com/ProverCoderAI/openapi-effect#readme", "packageManager": "pnpm@10.30.1", "dependencies": { - "@effect/cli": "^0.73.2", - "@effect/cluster": "^0.56.4", - "@effect/experimental": "^0.58.0", - "@effect/platform": "^0.94.5", - "@effect/platform-node": "^0.104.1", - "@effect/printer": "^0.47.0", - "@effect/printer-ansi": "^0.47.0", - "@effect/rpc": "^0.73.2", - "@effect/schema": "^0.75.5", - "@effect/sql": "^0.49.0", - "@effect/typeclass": "^0.38.0", - "@effect/workflow": "^0.16.0", "effect": "^3.19.18", - "openapi-typescript-helpers": "^0.1.0", - "ts-morph": "^27.0.2" + "openapi-typescript-helpers": "^0.1.0" }, "devDependencies": { "@biomejs/biome": "^2.4.4", "@effect/eslint-plugin": "^0.3.2", "@effect/language-service": "latest", - "@effect/vitest": "^0.27.0", "@eslint-community/eslint-plugin-eslint-comments": "^4.6.0", "@eslint/compat": "2.0.2", "@eslint/eslintrc": "3.3.3", "@eslint/js": "10.0.1", - "@prover-coder-ai/eslint-plugin-suggest-members": "^0.0.25", "@ton-ai-core/vibecode-linter": "^1.0.11", "@types/node": "^24.10.13", "@typescript-eslint/eslint-plugin": "^8.56.0", "@typescript-eslint/parser": "^8.56.0", - "typescript-eslint": "^8.56.0", "@vitest/coverage-v8": "^4.0.18", + "@vitest/eslint-plugin": "^1.6.9", "eslint": "^10.0.1", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-codegen": "0.34.1", @@ -90,10 +72,10 @@ "eslint-plugin-sonarjs": "^4.0.0", "eslint-plugin-sort-destructure-keys": "^3.0.0", "eslint-plugin-unicorn": "^63.0.0", - "@vitest/eslint-plugin": "^1.6.9", "globals": "^17.3.0", "jscpd": "^4.0.8", "typescript": "^5.9.3", + "typescript-eslint": "^8.56.0", "vite": "^7.3.1", "vite-tsconfig-paths": "^6.1.1", "vitest": "^4.0.18" diff --git a/packages/app/scripts/gen-strict-api.ts b/packages/app/scripts/gen-strict-api.ts deleted file mode 100644 index 3e90e50..0000000 --- a/packages/app/scripts/gen-strict-api.ts +++ /dev/null @@ -1,293 +0,0 @@ -// CHANGE: Generator script to create dispatcher and decoder files from OpenAPI schema -// WHY: Automate creation of type-safe dispatchers that maintain status→body correlation -// QUOTE(ТЗ): "генератор...гарантия: генерация детерминирована, коммитится в репозиторий" -// REF: issue-2, section 5.3 -// SOURCE: n/a -// FORMAT THEOREM: ∀ schema: generate(schema) → (dispatch.ts, decoders.ts) where typecheck(generated) = ✓ -// PURITY: SHELL -// EFFECT: File system operations -// INVARIANT: Generated code has no `any` or `unknown` in product code -// COMPLEXITY: O(n) where n = number of operations in schema - -import * as fs from "node:fs" -import * as path from "node:path" -import { Project } from "ts-morph" - -type OpenAPISpec = { - paths: Record> -} - -type OperationSpec = { - operationId?: string - responses: Record -} - -type ResponseSpec = { - description?: string - content?: Record -} - -type MediaTypeSpec = { - schema?: Record -} - -const OPENAPI_JSON_PATH = process.argv[2] ?? "tests/fixtures/petstore.openapi.json" -const OUTPUT_DIR = process.argv[3] ?? "src/generated" - -console.log(`Generating strict API client from: ${OPENAPI_JSON_PATH}`) -console.log(`Output directory: ${OUTPUT_DIR}`) - -// Read OpenAPI spec -const spec = JSON.parse(fs.readFileSync(OPENAPI_JSON_PATH, "utf-8")) as OpenAPISpec - -// Create output directory -fs.mkdirSync(OUTPUT_DIR, { recursive: true }) - -// Initialize ts-morph project -const project = new Project({ - compilerOptions: { - target: 99, // ESNext - module: 99, // ESNext - strict: true, - esModuleInterop: true - } -}) - -/** - * Generate dispatcher file with exhaustive switch for all statuses - */ -const generateDispatchFile = () => { - const sourceFile = project.createSourceFile( - path.join(OUTPUT_DIR, "dispatch.ts"), - "", - { overwrite: true } - ) - - sourceFile.addStatements(`// CHANGE: Auto-generated dispatchers for all operations -// WHY: Maintain compile-time correlation between status codes and body types -// QUOTE(ТЗ): "реализует switch(status) по всем статусам схемы" -// REF: issue-2, section 5.2 -// SOURCE: Generated from ${OPENAPI_JSON_PATH} -// FORMAT THEOREM: ∀ op ∈ Operations: dispatcher(op) handles all statuses in schema -// PURITY: SHELL -// EFFECT: Effect -// INVARIANT: Exhaustive coverage of all schema statuses and content-types -// COMPLEXITY: O(1) per dispatch (switch lookup) - -import { Effect } from "effect" -import type { Dispatcher } from "../shell/api-client/strict-client.js" -import { createDispatcher, parseJSON, unexpectedContentType, unexpectedStatus } from "../shell/api-client/strict-client.js" -import * as Decoders from "./decoders.js" -`) - - // Generate dispatcher for each operation - const operations: Array<{ path: string; method: string; operationId: string }> = [] - - for (const [pathKey, pathItem] of Object.entries(spec.paths)) { - for (const [method, operation] of Object.entries(pathItem)) { - const operationId = operation.operationId ?? `${method}${pathKey.replace(/[^a-zA-Z0-9]/g, "_")}` - operations.push({ path: pathKey, method, operationId }) - - // Collect all statuses and their content types - const responses = operation.responses - const statusHandlers: string[] = [] - - for (const [status, response] of Object.entries(responses)) { - const contentTypes = response.content ? Object.keys(response.content) : [] - - if (contentTypes.length === 0) { - // No content (e.g., 204) - statusHandlers.push( - ` case ${status}: - return Effect.succeed({ - status: ${status}, - contentType: "none" as const, - body: undefined as void - } as const)` - ) - } else if (contentTypes.length === 1) { - const ct = contentTypes[0]! - const ctCheck = ct === "application/json" ? 'contentType?.includes("application/json")' : `contentType === "${ct}"` - - statusHandlers.push( - ` case ${status}: - if (${ctCheck}) { - return Effect.gen(function* () { - const parsed = yield* parseJSON(status, "${ct}", text) - const decoded = yield* Decoders.decode${operationId}_${status}(status, "${ct}", text, parsed) - return { - status: ${status}, - contentType: "${ct}" as const, - body: decoded - } as const - }) - } - return Effect.fail(unexpectedContentType(status, ${JSON.stringify(contentTypes)}, contentType, text))` - ) - } else { - // Multiple content types - add inner switch - const ctCases = contentTypes.map((ct) => { - const ctCheck = ct === "application/json" ? 'contentType?.includes("application/json")' : `contentType === "${ct}"` - return ` if (${ctCheck}) { - return Effect.gen(function* () { - const parsed = yield* parseJSON(status, "${ct}", text) - const decoded = yield* Decoders.decode${operationId}_${status}_${ct.replace(/[^a-zA-Z0-9]/g, "_")}(status, "${ct}", text, parsed) - return { - status: ${status}, - contentType: "${ct}" as const, - body: decoded - } as const - }) - }` - }) - - statusHandlers.push( - ` case ${status}: -${ctCases.join("\n")} - return Effect.fail(unexpectedContentType(status, ${JSON.stringify(contentTypes)}, contentType, text))` - ) - } - } - - sourceFile.addStatements(` -/** - * Dispatcher for ${operationId} - * Handles statuses: ${Object.keys(responses).join(", ")} - * - * @pure false - applies decoders - * @invariant Exhaustive coverage of all schema statuses - */ -export const dispatcher${operationId}: Dispatcher = createDispatcher((status, contentType, text) => { - switch (status) { -${statusHandlers.join("\n")} - default: - return Effect.fail(unexpectedStatus(status, text)) - } -}) -`) - } - } - - sourceFile.formatText() - sourceFile.saveSync() - console.log(`✓ Generated dispatch.ts with ${operations.length} dispatchers`) -} - -/** - * Generate decoder stubs (to be replaced with real decoders when schema is available) - */ -const generateDecodersFile = () => { - const sourceFile = project.createSourceFile( - path.join(OUTPUT_DIR, "decoders.ts"), - "", - { overwrite: true } - ) - - sourceFile.addStatements(` -// CHANGE: Auto-generated decoder stubs for all operations -// WHY: Provide type-safe runtime validation entry points -// QUOTE(ТЗ): "при изменении схемы сборка обязана падать, пока декодеры не обновлены" -// REF: issue-2, section 5.2 -// SOURCE: Generated from ${OPENAPI_JSON_PATH} -// FORMAT THEOREM: ∀ op, status: decoder(op, status) → Effect -// PURITY: SHELL -// EFFECT: Effect -// INVARIANT: All decoders return typed DecodeError on failure -// COMPLEXITY: O(n) where n = size of parsed object - -import { Effect } from "effect" -import type { DecodeError } from "../core/api-client/strict-types.js" - -`.trimStart()) - - // Generate decoder stubs for each operation and status - for (const [pathKey, pathItem] of Object.entries(spec.paths)) { - for (const [method, operation] of Object.entries(pathItem)) { - const operationId = operation.operationId ?? `${method}${pathKey.replace(/[^a-zA-Z0-9]/g, "_")}` - - for (const [status, response] of Object.entries(operation.responses)) { - const contentTypes = response.content ? Object.keys(response.content) : [] - - if (contentTypes.length === 0) { - continue // No decoder needed for no-content responses - } - - for (const ct of contentTypes) { - const decoderName = - contentTypes.length === 1 - ? `decode${operationId}_${status}` - : `decode${operationId}_${status}_${ct.replace(/[^a-zA-Z0-9]/g, "_")}` - - sourceFile.addStatements(` -/** - * Decoder for ${operationId} status ${status} (${ct}) - * TODO: Replace stub with real schema decoder - * - * @pure false - performs validation - * @effect Effect - */ -export const ${decoderName} = ( - _status: number, - _contentType: string, - _body: string, - parsed: unknown -): Effect.Effect => { - // STUB: Always succeeds with parsed value - // Replace with: Schema.decodeUnknown(YourSchema)(parsed) - return Effect.succeed(parsed) - - // Example of real decoder: - // return Effect.mapError( - // Schema.decodeUnknown(YourSchema)(parsed), - // (error): DecodeError => ({ - // _tag: "DecodeError", - // status, - // contentType, - // error, - // body - // }) - // ) -} -`) - } - } - } - } - - sourceFile.formatText() - sourceFile.saveSync() - console.log("✓ Generated decoders.ts with stub decoders") -} - -/** - * Generate index file for generated module - */ -const generateIndexFile = () => { - const sourceFile = project.createSourceFile( - path.join(OUTPUT_DIR, "index.ts"), - "", - { overwrite: true } - ) - - sourceFile.addStatements(` -// CHANGE: Export all generated dispatchers and decoders -// WHY: Single entry point for generated code -// REF: issue-2 -// PURITY: CORE -// COMPLEXITY: O(1) - -export * from "./dispatch.js" -export * from "./decoders.js" -`.trimStart()) - - sourceFile.formatText() - sourceFile.saveSync() - console.log("✓ Generated index.ts") -} - -// Generate all files -generateDispatchFile() -generateDecodersFile() -generateIndexFile() - -console.log("✅ Generation complete!") diff --git a/packages/app/scripts/lint-types.sh b/packages/app/scripts/lint-types.sh index 6b5f6ae..c60c110 100755 --- a/packages/app/scripts/lint-types.sh +++ b/packages/app/scripts/lint-types.sh @@ -7,35 +7,43 @@ # Exit on error set -e -echo "Checking for any/unknown usage outside axioms.ts..." +echo "Checking for any usage and unknown usage outside typed boundaries..." -# Files allowed to contain any/unknown (Variant B policy) -ALLOWED_FILES=( +# Files allowed to contain unknown at runtime/type boundaries +UNKNOWN_ALLOWED_FILES=( "src/core/axioms.ts" + "src/shell/api-client/openapi-compat-request.ts" + "src/shell/api-client/openapi-compat-serializers.ts" + "src/shell/api-client/openapi-compat-path.ts" + "src/shell/api-client/openapi-compat-value-guards.ts" + "src/shell/api-client/create-client-runtime-types.ts" + "src/shell/api-client/create-client-runtime-helpers.ts" + "src/shell/api-client/create-client-runtime.ts" + "src/shell/api-client/create-client-middleware.ts" + "src/shell/api-client/create-client-types.ts" + "src/shell/api-client/create-client-response.ts" ) -# Pattern to find problematic any/unknown usage -# Excludes: -# - Type comments (/* any */) -# - JSDoc type comments (/** @type {any} */) -# - conditional extends unknown (idiomatic TypeScript) -PATTERN='(: any\b|as any\b|\bunknown\b)' +ANY_PATTERN='(: any\b|as any\b)' +UNKNOWN_PATTERN='\bunknown\b' -# Find all TypeScript files in src, excluding allowed files FOUND_VIOLATIONS="" for file in $(find src -name "*.ts" -type f); do - # Check if file is in allowed list - IS_ALLOWED=false - for allowed in "${ALLOWED_FILES[@]}"; do + ANY_MATCHES=$(grep -nE "$ANY_PATTERN" "$file" 2>/dev/null || true) + if [ -n "$ANY_MATCHES" ]; then + FOUND_VIOLATIONS="$FOUND_VIOLATIONS\n$file:\n$ANY_MATCHES\n" + fi + + IS_UNKNOWN_ALLOWED=false + for allowed in "${UNKNOWN_ALLOWED_FILES[@]}"; do if [[ "$file" == *"$allowed"* ]]; then - IS_ALLOWED=true + IS_UNKNOWN_ALLOWED=true break fi done - if [ "$IS_ALLOWED" = false ]; then - # Search for violations, excluding conditional type patterns - MATCHES=$(grep -nE "$PATTERN" "$file" 2>/dev/null | grep -vE 'extends.*unknown|Record' || true) + if [ "$IS_UNKNOWN_ALLOWED" = false ]; then + MATCHES=$(grep -nE "$UNKNOWN_PATTERN" "$file" 2>/dev/null | grep -vE 'extends.*unknown|Record' || true) if [ -n "$MATCHES" ]; then FOUND_VIOLATIONS="$FOUND_VIOLATIONS\n$file:\n$MATCHES\n" fi @@ -46,8 +54,8 @@ if [ -n "$FOUND_VIOLATIONS" ]; then echo -e "\n❌ Found any/unknown usage outside allowed files:" echo -e "$FOUND_VIOLATIONS" echo "" - echo "Allowed files: ${ALLOWED_FILES[*]}" - echo "Please move type casts to axioms.ts or eliminate the usage." + echo "Unknown-allowed files: ${UNKNOWN_ALLOWED_FILES[*]}" + echo "Please move boundary unknown usage to the listed modules or eliminate it." exit 1 else echo "✅ No any/unknown violations found!" diff --git a/packages/app/src/app/main.ts b/packages/app/src/app/main.ts deleted file mode 100644 index 3ab2b46..0000000 --- a/packages/app/src/app/main.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { NodeContext, NodeRuntime } from "@effect/platform-node" -import { Effect, pipe } from "effect" - -import { program } from "./program.js" - -// CHANGE: run the program through the Node platform runtime with its layer -// WHY: ensure effects execute under the platform runtime with proper teardown/logging behavior -// QUOTE(TZ): "\u0414\u0430 \u0434\u0430\u0432\u0430\u0439 \u0442\u0430\u043a \u044d\u0442\u043e \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f" -// REF: user-2025-12-19-platform-node -// SOURCE: https://effect.website/docs/platform/runtime/ "runMain helps you execute a main effect with built-in error handling, logging, and signal management." -// FORMAT THEOREM: forall args in Argv: decode(args) = v -> runMain(program) -// PURITY: SHELL -// EFFECT: Effect -// INVARIANT: program executed with NodeContext.layer -// COMPLEXITY: O(1)/O(1) -const main = pipe(program, Effect.provide(NodeContext.layer)) - -NodeRuntime.runMain(main) diff --git a/packages/app/src/app/program.ts b/packages/app/src/app/program.ts deleted file mode 100644 index 302e7eb..0000000 --- a/packages/app/src/app/program.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Console, Effect, pipe } from "effect" - -import { formatGreeting } from "../core/greeting.js" -import { readGreetingVariant } from "../shell/cli.js" - -/** - * Compose the CLI program as a single effect. - * - * @returns Effect that returns the greeting string and logs it once on success. - * - * @pure false - uses Console output - * @effect Console - * @invariant forall args in Argv: decode(args) = v -> logs exactly one greeting - * @precondition true - * @postcondition exists greeting: returned(greeting) and logged(greeting) - * @complexity O(1) - * @throws Never - all errors are typed in the Effect error channel - */ -// CHANGE: extract the composed program into a reusable Effect -// WHY: keep the entrypoint as a thin platform runtime shell and make testing deterministic -// QUOTE(TZ): "\u0414\u0430 \u0434\u0430\u0432\u0430\u0439 \u0442\u0430\u043a \u044d\u0442\u043e \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f" -// REF: user-2025-12-19-platform-node -// SOURCE: https://effect.website/docs/platform/runtime/ "runMain helps you execute a main effect with built-in error handling, logging, and signal management." -// FORMAT THEOREM: forall args in Argv: decode(args) = v -> log(formatGreeting(v)) -// PURITY: SHELL -// EFFECT: Effect -// INVARIANT: exactly one log entry per successful parse -// COMPLEXITY: O(1)/O(1) -export const program = pipe( - readGreetingVariant, - Effect.map((variant) => formatGreeting(variant)), - Effect.tap(Console.log) -) diff --git a/packages/app/src/core/axioms.ts b/packages/app/src/core/axioms.ts index 81242fe..aa1c066 100644 --- a/packages/app/src/core/axioms.ts +++ b/packages/app/src/core/axioms.ts @@ -13,21 +13,6 @@ * JSON value type - result of JSON.parse() * This is the fundamental type for all parsed JSON values */ -/** - * Cast function for dispatcher factory - * AXIOM: Dispatcher factory receives valid classify function - * - * This enables generated dispatchers to work with heterogeneous Effect unions. - * The cast is safe because: - * 1. The classify function is generated from OpenAPI schema - * 2. All status/content-type combinations are exhaustively covered - * 3. The returned Effect conforms to Dispatcher signature - * - * @pure true - */ -import type { Effect } from "effect" -import type { ApiFailure, ApiSuccess, TransportError } from "./api-client/strict-types.js" - export type Json = | null | boolean @@ -46,81 +31,6 @@ export type Json = */ export const asJson = (value: unknown): Json => value as Json -/** - * Cast a value to a specific type with const assertion - * Used for creating literal typed objects in generated code - * - * @pure true - */ -export const asConst = (value: T): T => value - -/** - * Create a typed RawResponse from raw values - * AXIOM: HTTP response structure is known at runtime - * - * @pure true - */ -export type RawResponse = { - readonly status: number - readonly headers: Headers - readonly text: string -} - -export const asRawResponse = (value: { - status: number - headers: Headers - text: string -}): RawResponse => value as RawResponse - -/** - * Dispatcher classifies response and applies decoder - * - * NEW DESIGN (Effect-native): - * - Success channel: `ApiSuccess` (2xx responses only) - * - Error channel: `ApiFailure` (non-2xx schema errors + boundary errors) - * - * This forces developers to explicitly handle HTTP errors (404, 500, etc.) - * using Effect.catchTag, Effect.match, or similar patterns. - * - * @pure false - applies decoders - * @effect Effect - * @invariant Must handle all statuses and content-types from schema - */ -export type Dispatcher = ( - response: RawResponse -) => Effect.Effect< - ApiSuccess, - Exclude, TransportError> -> - -export const asDispatcher = ( - fn: (response: RawResponse) => Effect.Effect -): Dispatcher => fn as Dispatcher - -/** - * Cast for StrictRequestInit config object - * AXIOM: Config object has correct structure when all properties assigned - * - * @pure true - */ -export const asStrictRequestInit = (config: object): T => config as T - -/** - * Classifier function type for dispatcher creation - * AXIOM: Classify function returns Effect with heterogeneous union types - * - * This type uses `unknown` to allow the classify function to return - * heterogeneous Effect unions from switch statements. The actual types - * are enforced by the generated dispatcher code. - * - * @pure true - */ -export type ClassifyFn = ( - status: number, - contentType: string | undefined, - text: string -) => Effect.Effect - /** * Cast internal client implementation to typed StrictApiClient * AXIOM: Client implementation correctly implements all method constraints @@ -134,14 +44,6 @@ export type ClassifyFn = ( */ export const asStrictApiClient = (client: object): T => client as T -/** - * Cast default dispatchers registry to specific schema type - * AXIOM: Default dispatcher registry was registered for the current Paths type - * - * @pure true - */ -export const asDispatchersFor = (value: unknown): T => value as T - /** * Cast middleware callback output after async boundary normalization. * AXIOM: Middleware runtime validation checks the concrete Request/Response/Error diff --git a/packages/app/src/core/greeting.ts b/packages/app/src/core/greeting.ts deleted file mode 100644 index 68f6502..0000000 --- a/packages/app/src/core/greeting.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Match } from "effect" - -export type GreetingVariant = - | { readonly kind: "effect" } - | { readonly kind: "named"; readonly name: string } - -/** - * Formats a greeting message without side effects. - * - * @param variant - Non-empty, classified name information. - * @returns Greeting text composed deterministically. - * - * @pure true - * @invariant variant.kind === "named" ⇒ variant.name.length > 0 - * @complexity O(1) time / O(1) space - */ -export const formatGreeting = (variant: GreetingVariant): string => - Match.value(variant).pipe( - Match.when({ kind: "effect" }, () => "Hello from Effect!"), - Match.when({ kind: "named" }, ({ name }) => `Hello, ${name}!`), - Match.exhaustive - ) diff --git a/packages/app/src/generated/decoders.ts b/packages/app/src/generated/decoders.ts deleted file mode 100644 index 43605c1..0000000 --- a/packages/app/src/generated/decoders.ts +++ /dev/null @@ -1,318 +0,0 @@ -// CHANGE: Auto-generated decoder stubs for all operations -// WHY: Provide type-safe runtime validation entry points -// QUOTE(ТЗ): "при изменении схемы сборка обязана падать, пока декодеры не обновлены" -// REF: issue-2, section 5.2 -// SOURCE: Generated from tests/fixtures/petstore.openapi.json -// FORMAT THEOREM: ∀ op, status: decoder(op, status) → Effect -// PURITY: SHELL -// EFFECT: Effect -// INVARIANT: All decoders return typed DecodeError on failure -// COMPLEXITY: O(n) where n = size of parsed object - -import { Effect } from "effect" -import type { DecodeError } from "../core/api-client/strict-types.js" - -/** - * JSON value type - result of JSON.parse() - */ -type Json = null | boolean | number | string | ReadonlyArray | { readonly [k: string]: Json } - -/** - * Decoder for listPets status 200 (application/json) - * STUB: Replace with real schema decoder when needed - * - * @pure false - performs validation - * @effect Effect - */ -export const decodelistPets_200 = ( - _status: number, - _contentType: string, - _body: string, - parsed: Json -): Effect.Effect => { - // STUB: Always succeeds with parsed value - // Replace with: Schema.decodeUnknown(YourSchema)(parsed) - return Effect.succeed(parsed) - - // Example of real decoder: - // return Effect.mapError( - // Schema.decodeUnknown(YourSchema)(parsed), - // (error): DecodeError => ({ - // _tag: "DecodeError", - // status, - // contentType, - // error, - // body - // }) - // ) -} - -/** - * Decoder for listPets status 500 (application/json) - * STUB: Replace with real schema decoder when needed - * - * @pure false - performs validation - * @effect Effect - */ -export const decodelistPets_500 = ( - _status: number, - _contentType: string, - _body: string, - parsed: Json -): Effect.Effect => { - // STUB: Always succeeds with parsed value - // Replace with: Schema.decodeUnknown(YourSchema)(parsed) - return Effect.succeed(parsed) - - // Example of real decoder: - // return Effect.mapError( - // Schema.decodeUnknown(YourSchema)(parsed), - // (error): DecodeError => ({ - // _tag: "DecodeError", - // status, - // contentType, - // error, - // body - // }) - // ) -} - -/** - * Decoder for createPet status 201 (application/json) - * STUB: Replace with real schema decoder when needed - * - * @pure false - performs validation - * @effect Effect - */ -export const decodecreatePet_201 = ( - _status: number, - _contentType: string, - _body: string, - parsed: Json -): Effect.Effect => { - // STUB: Always succeeds with parsed value - // Replace with: Schema.decodeUnknown(YourSchema)(parsed) - return Effect.succeed(parsed) - - // Example of real decoder: - // return Effect.mapError( - // Schema.decodeUnknown(YourSchema)(parsed), - // (error): DecodeError => ({ - // _tag: "DecodeError", - // status, - // contentType, - // error, - // body - // }) - // ) -} - -/** - * Decoder for createPet status 400 (application/json) - * STUB: Replace with real schema decoder when needed - * - * @pure false - performs validation - * @effect Effect - */ -export const decodecreatePet_400 = ( - _status: number, - _contentType: string, - _body: string, - parsed: Json -): Effect.Effect => { - // STUB: Always succeeds with parsed value - // Replace with: Schema.decodeUnknown(YourSchema)(parsed) - return Effect.succeed(parsed) - - // Example of real decoder: - // return Effect.mapError( - // Schema.decodeUnknown(YourSchema)(parsed), - // (error): DecodeError => ({ - // _tag: "DecodeError", - // status, - // contentType, - // error, - // body - // }) - // ) -} - -/** - * Decoder for createPet status 500 (application/json) - * STUB: Replace with real schema decoder when needed - * - * @pure false - performs validation - * @effect Effect - */ -export const decodecreatePet_500 = ( - _status: number, - _contentType: string, - _body: string, - parsed: Json -): Effect.Effect => { - // STUB: Always succeeds with parsed value - // Replace with: Schema.decodeUnknown(YourSchema)(parsed) - return Effect.succeed(parsed) - - // Example of real decoder: - // return Effect.mapError( - // Schema.decodeUnknown(YourSchema)(parsed), - // (error): DecodeError => ({ - // _tag: "DecodeError", - // status, - // contentType, - // error, - // body - // }) - // ) -} - -/** - * Decoder for getPet status 200 (application/json) - * STUB: Replace with real schema decoder when needed - * - * @pure false - performs validation - * @effect Effect - */ -export const decodegetPet_200 = ( - _status: number, - _contentType: string, - _body: string, - parsed: Json -): Effect.Effect => { - // STUB: Always succeeds with parsed value - // Replace with: Schema.decodeUnknown(YourSchema)(parsed) - return Effect.succeed(parsed) - - // Example of real decoder: - // return Effect.mapError( - // Schema.decodeUnknown(YourSchema)(parsed), - // (error): DecodeError => ({ - // _tag: "DecodeError", - // status, - // contentType, - // error, - // body - // }) - // ) -} - -/** - * Decoder for getPet status 404 (application/json) - * STUB: Replace with real schema decoder when needed - * - * @pure false - performs validation - * @effect Effect - */ -export const decodegetPet_404 = ( - _status: number, - _contentType: string, - _body: string, - parsed: Json -): Effect.Effect => { - // STUB: Always succeeds with parsed value - // Replace with: Schema.decodeUnknown(YourSchema)(parsed) - return Effect.succeed(parsed) - - // Example of real decoder: - // return Effect.mapError( - // Schema.decodeUnknown(YourSchema)(parsed), - // (error): DecodeError => ({ - // _tag: "DecodeError", - // status, - // contentType, - // error, - // body - // }) - // ) -} - -/** - * Decoder for getPet status 500 (application/json) - * STUB: Replace with real schema decoder when needed - * - * @pure false - performs validation - * @effect Effect - */ -export const decodegetPet_500 = ( - _status: number, - _contentType: string, - _body: string, - parsed: Json -): Effect.Effect => { - // STUB: Always succeeds with parsed value - // Replace with: Schema.decodeUnknown(YourSchema)(parsed) - return Effect.succeed(parsed) - - // Example of real decoder: - // return Effect.mapError( - // Schema.decodeUnknown(YourSchema)(parsed), - // (error): DecodeError => ({ - // _tag: "DecodeError", - // status, - // contentType, - // error, - // body - // }) - // ) -} - -/** - * Decoder for deletePet status 404 (application/json) - * STUB: Replace with real schema decoder when needed - * - * @pure false - performs validation - * @effect Effect - */ -export const decodedeletePet_404 = ( - _status: number, - _contentType: string, - _body: string, - parsed: Json -): Effect.Effect => { - // STUB: Always succeeds with parsed value - // Replace with: Schema.decodeUnknown(YourSchema)(parsed) - return Effect.succeed(parsed) - - // Example of real decoder: - // return Effect.mapError( - // Schema.decodeUnknown(YourSchema)(parsed), - // (error): DecodeError => ({ - // _tag: "DecodeError", - // status, - // contentType, - // error, - // body - // }) - // ) -} - -/** - * Decoder for deletePet status 500 (application/json) - * STUB: Replace with real schema decoder when needed - * - * @pure false - performs validation - * @effect Effect - */ -export const decodedeletePet_500 = ( - _status: number, - _contentType: string, - _body: string, - parsed: Json -): Effect.Effect => { - // STUB: Always succeeds with parsed value - // Replace with: Schema.decodeUnknown(YourSchema)(parsed) - return Effect.succeed(parsed) - - // Example of real decoder: - // return Effect.mapError( - // Schema.decodeUnknown(YourSchema)(parsed), - // (error): DecodeError => ({ - // _tag: "DecodeError", - // status, - // contentType, - // error, - // body - // }) - // ) -} diff --git a/packages/app/src/generated/dispatch.ts b/packages/app/src/generated/dispatch.ts deleted file mode 100644 index 0e2b102..0000000 --- a/packages/app/src/generated/dispatch.ts +++ /dev/null @@ -1,172 +0,0 @@ -// CHANGE: Auto-generated dispatchers for all operations with Effect-native error handling -// WHY: Maintain compile-time correlation between status codes and body types -// QUOTE(ТЗ): "реализует switch(status) по всем статусам схемы; Failure включает все инварианты протокола и схемы" -// REF: issue-2, section 5.2, 4.1-4.3 -// SOURCE: Generated from tests/fixtures/petstore.openapi.json -// FORMAT THEOREM: ∀ op ∈ Operations: dispatcher(op) → Effect -// PURITY: SHELL -// EFFECT: Effect, HttpError | BoundaryError, never> -// INVARIANT: 2xx → success channel, non-2xx → error channel (forced handling) -// COMPLEXITY: O(1) per dispatch (Match lookup) - -import { Effect, Match } from "effect" -import type { Operations } from "../../tests/fixtures/petstore.openapi.js" -import type { DecodeError, ResponsesFor } from "../core/api-client/strict-types.js" -import { asConst, type Json } from "../core/axioms.js" -import { - createDispatcher, - parseJSON, - unexpectedContentType, - unexpectedStatus -} from "../shell/api-client/strict-client.js" -import * as Decoders from "./decoders.js" - -// Response types for each operation - used for type inference -type ListPetsResponses = ResponsesFor -type CreatePetResponses = ResponsesFor -type GetPetResponses = ResponsesFor -type DeletePetResponses = ResponsesFor - -/** - * Helper: process JSON content type for a given status - returns SUCCESS variant - * Used for 2xx responses that go to the success channel - */ -const processJsonContentSuccess = ( - status: S, - contentType: string | undefined, - text: string, - decoder: ( - s: number, - ct: string, - body: string, - parsed: Json - ) => Effect.Effect -) => - contentType?.includes("application/json") - ? Effect.gen(function*() { - const parsed = yield* parseJSON(status, "application/json", text) - const decoded = yield* decoder(status, "application/json", text, parsed) - return asConst({ - status, - contentType: "application/json" as const, - body: decoded - }) - }) - : Effect.fail(unexpectedContentType(status, ["application/json"], contentType, text)) - -/** - * Helper: process JSON content type for a given status - returns HTTP ERROR variant - * Used for non-2xx responses (4xx, 5xx) that go to the error channel. - * - * Adds `_tag: "HttpError"` discriminator to distinguish from BoundaryError. - */ -const processJsonContentError = ( - status: S, - contentType: string | undefined, - text: string, - decoder: ( - s: number, - ct: string, - body: string, - parsed: Json - ) => Effect.Effect -) => - contentType?.includes("application/json") - ? Effect.gen(function*() { - const parsed = yield* parseJSON(status, "application/json", text) - const decoded = yield* decoder(status, "application/json", text, parsed) - // Non-2xx: Return as FAILURE with _tag discriminator (goes to error channel) - return yield* Effect.fail(asConst({ - _tag: "HttpError" as const, - status, - contentType: "application/json" as const, - body: decoded - })) - }) - : Effect.fail(unexpectedContentType(status, ["application/json"], contentType, text)) - -/** - * Dispatcher for listPets - * Handles statuses: 200 (success), 500 (error) - * - * Effect channel mapping: - * - 200: success channel → ApiSuccess - * - 500: error channel → HttpError (forces explicit handling) - * - * @pure false - applies decoders - * @invariant Exhaustive coverage of all schema statuses - */ -export const dispatcherlistPets = createDispatcher((status, contentType, text) => - Match.value(status).pipe( - Match.when(200, () => processJsonContentSuccess(200, contentType, text, Decoders.decodelistPets_200)), - Match.when(500, () => processJsonContentError(500, contentType, text, Decoders.decodelistPets_500)), - Match.orElse(() => Effect.fail(unexpectedStatus(status, text))) - ) -) - -/** - * Dispatcher for createPet - * Handles statuses: 201 (success), 400 (error), 500 (error) - * - * Effect channel mapping: - * - 201: success channel → ApiSuccess - * - 400, 500: error channel → HttpError (forces explicit handling) - * - * @pure false - applies decoders - * @invariant Exhaustive coverage of all schema statuses - */ -export const dispatchercreatePet = createDispatcher((status, contentType, text) => - Match.value(status).pipe( - Match.when(201, () => processJsonContentSuccess(201, contentType, text, Decoders.decodecreatePet_201)), - Match.when(400, () => processJsonContentError(400, contentType, text, Decoders.decodecreatePet_400)), - Match.when(500, () => processJsonContentError(500, contentType, text, Decoders.decodecreatePet_500)), - Match.orElse(() => Effect.fail(unexpectedStatus(status, text))) - ) -) - -/** - * Dispatcher for getPet - * Handles statuses: 200 (success), 404 (error), 500 (error) - * - * Effect channel mapping: - * - 200: success channel → ApiSuccess - * - 404, 500: error channel → HttpError (forces explicit handling) - * - * @pure false - applies decoders - * @invariant Exhaustive coverage of all schema statuses - */ -export const dispatchergetPet = createDispatcher((status, contentType, text) => - Match.value(status).pipe( - Match.when(200, () => processJsonContentSuccess(200, contentType, text, Decoders.decodegetPet_200)), - Match.when(404, () => processJsonContentError(404, contentType, text, Decoders.decodegetPet_404)), - Match.when(500, () => processJsonContentError(500, contentType, text, Decoders.decodegetPet_500)), - Match.orElse(() => Effect.fail(unexpectedStatus(status, text))) - ) -) - -/** - * Dispatcher for deletePet - * Handles statuses: 204 (success), 404 (error), 500 (error) - * - * Effect channel mapping: - * - 204: success channel → ApiSuccess (no content) - * - 404, 500: error channel → HttpError (forces explicit handling) - * - * @pure false - applies decoders - * @invariant Exhaustive coverage of all schema statuses - */ -export const dispatcherdeletePet = createDispatcher((status, contentType, text) => - Match.value(status).pipe( - Match.when(204, () => - Effect.succeed( - asConst({ - status: 204, - contentType: "none" as const, - body: undefined - }) - )), - Match.when(404, () => processJsonContentError(404, contentType, text, Decoders.decodedeletePet_404)), - Match.when(500, () => processJsonContentError(500, contentType, text, Decoders.decodedeletePet_500)), - Match.orElse(() => Effect.fail(unexpectedStatus(status, text))) - ) -) diff --git a/packages/app/src/generated/dispatchers-by-path.ts b/packages/app/src/generated/dispatchers-by-path.ts deleted file mode 100644 index 9c76c67..0000000 --- a/packages/app/src/generated/dispatchers-by-path.ts +++ /dev/null @@ -1,40 +0,0 @@ -// CHANGE: Auto-generated dispatcher map by path+method -// WHY: Provide a single dispatcher registry without manual wiring in examples -// QUOTE(ТЗ): "Этого в плане вообще не должно быть" -// REF: user-msg-3 -// SOURCE: Generated from tests/fixtures/petstore.openapi.json -// FORMAT THEOREM: ∀ path, method: dispatchersByPath[path][method] = dispatcher(op) -// PURITY: SHELL -// EFFECT: none -// INVARIANT: dispatcher map is total for all operations in Paths -// COMPLEXITY: O(1) - -import type { Paths } from "../../tests/fixtures/petstore.openapi.js" -import { type DispatchersFor, registerDefaultDispatchers } from "../shell/api-client/create-client.js" -import { dispatchercreatePet, dispatcherdeletePet, dispatchergetPet, dispatcherlistPets } from "./dispatch.js" - -/** - * Dispatcher map keyed by OpenAPI path and HTTP method - */ -export const dispatchersByPath: DispatchersFor = { - "/pets": { - get: dispatcherlistPets, - post: dispatchercreatePet - }, - "/pets/{petId}": { - get: dispatchergetPet, - delete: dispatcherdeletePet - } -} - -// CHANGE: Register default dispatchers at module load -// WHY: Enable createClient(options) without passing dispatcher map -// QUOTE(ТЗ): "const apiClient = createClient(clientOptions)" -// REF: user-msg-4 -// SOURCE: n/a -// FORMAT THEOREM: ∀ call: createClient(options) uses dispatchersByPath -// PURITY: SHELL -// EFFECT: none -// INVARIANT: registerDefaultDispatchers is called exactly once per module load -// COMPLEXITY: O(1) -registerDefaultDispatchers(dispatchersByPath) diff --git a/packages/app/src/generated/index.ts b/packages/app/src/generated/index.ts deleted file mode 100644 index 547e91c..0000000 --- a/packages/app/src/generated/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -// CHANGE: Export all generated dispatchers and decoders -// WHY: Single entry point for generated code -// REF: issue-2 -// PURITY: CORE -// COMPLEXITY: O(1) - -export * from "./decoders.js" -export * from "./dispatch.js" -export * from "./dispatchers-by-path.js" diff --git a/packages/app/src/index.ts b/packages/app/src/index.ts index e2d5f7a..648cfed 100644 --- a/packages/app/src/index.ts +++ b/packages/app/src/index.ts @@ -1,58 +1,37 @@ -// CHANGE: Expose Effect-only public API -// WHY: Enforce Effect-first paradigm and remove Promise-based client surface +// CHANGE: Expose the minimal openapi-effect public API +// WHY: Keep the package focused on openapi-fetch-compatible inputs with Effect output channels // SOURCE: n/a // PURITY: SHELL (re-exports) // COMPLEXITY: O(1) -export * as FetchHttpClient from "@effect/platform/FetchHttpClient" - export type * from "./core/api-client/index.js" export { assertNever } from "./core/api-client/index.js" export type { - Client, ClientEffect, ClientOptions, - DispatchersFor, EffectClient, EffectClientMethod, EffectClientRequestMethod, FetchOptions, - FetchResponse, + HeadersOptions, Middleware, - PathBasedClient, + ParseAs, QuerySerializer, QuerySerializerOptions, - StrictApiClient, - StrictApiClientWithDispatchers + RequestBodyOption, + RequestOptions } from "./shell/api-client/create-client.js" -export type { Decoder, Dispatcher, RawResponse, StrictClient, StrictRequestInit } from "./shell/api-client/index.js" - export { - createClient, createClientEffect, - createDispatcher, createFinalURL, - createPathBasedClient, createQuerySerializer, - createStrictClient, - createUniversalDispatcher, defaultBodySerializer, defaultPathSerializer, - executeRequest, mergeHeaders, - parseJSON, - registerDefaultDispatchers, removeTrailingSlash, serializeArrayParam, serializeObjectParam, - serializePrimitiveParam, - unexpectedContentType, - unexpectedStatus + serializePrimitiveParam } from "./shell/api-client/index.js" - -export { createClient as default } from "./shell/api-client/index.js" - -// Generated dispatchers (auto-generated from OpenAPI schema) -export * from "./generated/index.js" diff --git a/packages/app/src/shell/api-client/create-client-response.ts b/packages/app/src/shell/api-client/create-client-response.ts index e9117f4..7e84da9 100644 --- a/packages/app/src/shell/api-client/create-client-response.ts +++ b/packages/app/src/shell/api-client/create-client-response.ts @@ -10,12 +10,6 @@ import { asJson } from "../../core/axioms.js" import type { RuntimeApiSuccess, RuntimeEffectFailure, RuntimeHttpError } from "./create-client-runtime-types.js" import type { ParseAs } from "./create-client-types.js" -type RuntimeFetchResponse = { - data?: unknown - error?: unknown - response: Response -} - export const toError = (error: unknown): Error => ( error instanceof Error ? error : new Error(String(error)) ) @@ -25,22 +19,6 @@ export const toTransportError = (error: unknown): TransportError => ({ error: toError(error) }) -const parseJsonText = (rawText: string): Effect.Effect => ( - rawText.length === 0 - ? Effect.void - : Effect.try({ - try: () => asJson(JSON.parse(rawText)), - catch: toError - }) -) - -const readResponseText = (response: Response): Effect.Effect => ( - Effect.tryPromise({ - try: () => response.text(), - catch: toError - }) -) - const readResponseTextStrict = (response: Response): Effect.Effect => ( Effect.tryPromise({ try: () => response.text(), @@ -48,53 +26,6 @@ const readResponseTextStrict = (response: Response): Effect.Effect => { - if (parseAs === "stream") { - return Effect.succeed(response.body) - } - - if (parseAs === "text") { - return Effect.tryPromise({ try: () => response.text(), catch: toError }) - } - - if (parseAs === "blob") { - return Effect.tryPromise({ try: () => response.blob(), catch: toError }) - } - - if (parseAs === "arrayBuffer") { - return Effect.tryPromise({ try: () => response.arrayBuffer(), catch: toError }) - } - - if (contentLength === null) { - return readResponseText(response).pipe( - Effect.flatMap((rawText) => parseJsonText(rawText)) - ) - } - - return Effect.tryPromise({ try: () => response.json(), catch: toError }) -} - -const parseErrorData = (response: Response): Effect.Effect => ( - readResponseText(response).pipe( - Effect.flatMap((rawText) => - Effect.match( - Effect.try({ - try: () => asJson(JSON.parse(rawText)), - catch: toError - }), - { - onFailure: () => rawText, - onSuccess: (parsed) => parsed - } - ) - ) - ) -) - const hasChunkedTransferEncoding = (response: Response): boolean => ( response.headers.get("Transfer-Encoding")?.includes("chunked") === true ) @@ -109,30 +40,6 @@ const isEmptyResponse = ( || (contentLength === "0" && !hasChunkedTransferEncoding(response)) ) -export const createResponseEnvelope = ( - request: Request, - response: Response, - parseAs: ParseAs -): Effect.Effect => { - const contentLength = response.headers.get("Content-Length") - - if (isEmptyResponse(request, response, contentLength)) { - return response.ok - ? Effect.succeed({ data: undefined, response }) - : Effect.succeed({ error: undefined, response }) - } - - if (response.ok) { - return parseSuccessData(response, parseAs, contentLength).pipe( - Effect.map((data) => ({ data, response })) - ) - } - - return parseErrorData(response).pipe( - Effect.map((error) => ({ error, response })) - ) -} - const normalizeContentType = (value: string | null): string | undefined => ( value?.split(";")[0]?.trim().toLowerCase() ) diff --git a/packages/app/src/shell/api-client/create-client-runtime-types.ts b/packages/app/src/shell/api-client/create-client-runtime-types.ts index 5d0aa3a..1f6ebad 100644 --- a/packages/app/src/shell/api-client/create-client-runtime-types.ts +++ b/packages/app/src/shell/api-client/create-client-runtime-types.ts @@ -14,12 +14,6 @@ import type { QuerySerializerOptions } from "./create-client-types.js" -export type RuntimeFetchResponse = { - data?: unknown - error?: unknown - response: Response -} - export type RuntimeApiSuccess = { readonly status: number readonly contentType: string @@ -62,7 +56,6 @@ export type RuntimeClientFor = { eject: (...middleware: Array) => void } -export type RuntimeClient = RuntimeClientFor export type RuntimeEffectClient = RuntimeClientFor export type HeaderValue = diff --git a/packages/app/src/shell/api-client/create-client-runtime.ts b/packages/app/src/shell/api-client/create-client-runtime.ts index 252fe63..91fa759 100644 --- a/packages/app/src/shell/api-client/create-client-runtime.ts +++ b/packages/app/src/shell/api-client/create-client-runtime.ts @@ -1,7 +1,7 @@ import { Effect } from "effect" import { applyErrorMiddleware, applyRequestMiddleware, applyResponseMiddleware } from "./create-client-middleware.js" -import { createResponseEnvelope, createStrictResponseEffect, toTransportError } from "./create-client-response.js" +import { createStrictResponseEffect, toTransportError } from "./create-client-response.js" import { createMergedOptions, invokeFetch, @@ -17,10 +17,8 @@ import { createClientMethods } from "./create-client-runtime-methods.js" import type { BaseRuntimeConfig, PreparedRequest, - RuntimeClient, RuntimeEffectClient, - RuntimeFetchOptions, - RuntimeFetchResponse + RuntimeFetchOptions } from "./create-client-runtime-types.js" import type { BodySerializer, @@ -234,17 +232,6 @@ const executeFetch = ( }) } -const createCoreFetch = (config: BaseRuntimeConfig) => -( - schemaPath: string, - fetchOptions?: RuntimeFetchOptions -): Effect.Effect => - Effect.gen(function*() { - const prepared = prepareRequest(config, schemaPath, fetchOptions) - const execution = yield* executeFetch(prepared) - return yield* createResponseEnvelope(execution.request, execution.response, prepared.parseAs) - }) - const createCoreEffectFetch = (config: BaseRuntimeConfig) => ( schemaPath: string, @@ -288,13 +275,6 @@ const createBaseRuntimeConfig = ( } } -export const createRuntimeClient = (clientOptions?: ClientOptions): RuntimeClient => { - const globalMiddlewares: Array = [] - const config = createBaseRuntimeConfig(clientOptions, globalMiddlewares) - const coreFetch = createCoreFetch(config) - return createClientMethods(coreFetch, globalMiddlewares) -} - export const createRuntimeEffectClient = (clientOptions?: ClientOptions): RuntimeEffectClient => { const globalMiddlewares: Array = [] const config = createBaseRuntimeConfig(clientOptions, globalMiddlewares) diff --git a/packages/app/src/shell/api-client/create-client-types.ts b/packages/app/src/shell/api-client/create-client-types.ts index e6865f4..da0d4a1 100644 --- a/packages/app/src/shell/api-client/create-client-types.ts +++ b/packages/app/src/shell/api-client/create-client-types.ts @@ -1,16 +1,10 @@ -import type { Effect } from "effect" import type { - ErrorResponse, FilterKeys, HttpMethod, IsOperationRequestBodyOptional, - MediaType, OperationRequestBodyContent, PathsWithMethod, - Readable, RequiredKeysOf, - ResponseObjectMap, - SuccessResponse, Writable } from "openapi-typescript-helpers" @@ -70,9 +64,6 @@ type BodyType = { export type ParseAs = keyof BodyType -export type ParseAsResponse = Options extends { parseAs: ParseAs } ? BodyType[Options["parseAs"]] - : T - export interface DefaultParamsOption { params?: { query?: Record @@ -90,22 +81,6 @@ export type RequestBodyOption = Writable> exte export type FetchOptions = RequestOptions & Omit -export type FetchResponse< - T extends Record, - Options, - Media extends MediaType -> = - | { - data: ParseAsResponse, Media>>, Options> - error?: never - response: Response - } - | { - data?: never - error: Readable, Media>> - response: Response - } - export type RequestOptions = & ParamsOption & RequestBodyOption @@ -197,17 +172,6 @@ export type OperationFor< > = Paths[Path] extends Record ? Operation & Record : never -type MethodResult< - Paths extends object, - Path extends PathsWithMethod, - Method extends HttpMethod, - Init, - Media extends MediaType -> = Effect.Effect< - FetchResponse, Init, Media>, - Error -> - export type MethodArgs< Paths extends object, Method extends HttpMethod, @@ -219,92 +183,3 @@ export type RequestMethodArgs = < - Path extends PathsWithMethod, - Init extends MaybeOptionalInit> ->( - ...args: MethodArgs -) => MethodResult - -export type ClientRequestMethod< - Paths extends object, - Media extends MediaType -> = < - Method extends HttpMethod, - Path extends PathsWithMethod, - Init extends MaybeOptionalInit> ->( - ...args: RequestMethodArgs> -) => MethodResult - -type PathMethodResult< - PathInfo extends Record, - Method extends keyof PathInfo, - Init, - Media extends MediaType -> = Effect.Effect< - FetchResponse, Init, Media>, - Error -> - -export type ClientForPath, Media extends MediaType> = { - [Method in keyof PathInfo as Uppercase]: < - Init extends MaybeOptionalInit - >( - ...init: InitParam - ) => PathMethodResult -} - -export interface Client { - request: ClientRequestMethod - GET: ClientMethod - PUT: ClientMethod - POST: ClientMethod - DELETE: ClientMethod - OPTIONS: ClientMethod - HEAD: ClientMethod - PATCH: ClientMethod - TRACE: ClientMethod - use(...middleware: Array): void - eject(...middleware: Array): void -} - -export type ClientPathsWithMethod< - CreatedClient extends Client>>, - Method extends HttpMethod -> = CreatedClient extends Client ? PathsWithMethod - : never - -export type MethodResponse< - CreatedClient extends Client>>, - Method extends HttpMethod, - Path extends ClientPathsWithMethod, - Options = object -> = CreatedClient extends Client< - infer Paths extends Record>, - infer Media extends MediaType -> ? NonNullable< - FetchResponse, Options, Media>["data"] - > - : never - -export type PathBasedClient< - Paths extends Record, - Media extends MediaType = MediaType -> = { - [Path in keyof Paths]: ClientForPath, Media> -} - -export type DispatchersFor = { - [Path in keyof Paths]?: { - [Method in HttpMethod]?: object - } -} - -export type StrictApiClient = Client -export type StrictApiClientWithDispatchers = Client diff --git a/packages/app/src/shell/api-client/create-client.ts b/packages/app/src/shell/api-client/create-client.ts index 5f9ba5d..6bf142c 100644 --- a/packages/app/src/shell/api-client/create-client.ts +++ b/packages/app/src/shell/api-client/create-client.ts @@ -1,33 +1,19 @@ -import type { MediaType } from "openapi-typescript-helpers" - import { asStrictApiClient } from "../../core/axioms.js" import type { ClientEffect, EffectClient } from "./create-client-effect-types.js" -import type { RuntimeClient, RuntimeFetchOptions } from "./create-client-runtime-types.js" -import { createRuntimeClient, createRuntimeEffectClient } from "./create-client-runtime.js" -import type { Client, ClientOptions, DispatchersFor, PathBasedClient } from "./create-client-types.js" +import { createRuntimeEffectClient } from "./create-client-runtime.js" +import type { ClientOptions } from "./create-client-types.js" export type { - Client, - ClientForPath, - ClientMethod, ClientOptions, - ClientPathsWithMethod, - ClientRequestMethod, - DispatchersFor, FetchOptions, - FetchResponse, HeadersOptions, - MethodResponse, Middleware, MiddlewareCallbackParams, ParseAs, - PathBasedClient, QuerySerializer, QuerySerializerOptions, RequestBodyOption, - RequestOptions, - StrictApiClient, - StrictApiClientWithDispatchers + RequestOptions } from "./create-client-types.js" export type { @@ -49,69 +35,6 @@ export { serializePrimitiveParam } from "./openapi-compat-utils.js" -export const createClient = ( - clientOptions?: ClientOptions -): Client => asStrictApiClient>(createRuntimeClient(clientOptions)) - -class PathCallForwarder { - constructor( - private readonly client: RuntimeClient, - private readonly url: string - ) {} - - private readonly call = ( - method: "GET" | "PUT" | "POST" | "DELETE" | "OPTIONS" | "HEAD" | "PATCH" | "TRACE", - init?: RuntimeFetchOptions - ) => this.client[method](this.url, init) - - public readonly GET = (init?: RuntimeFetchOptions) => this.call("GET", init) - public readonly PUT = (init?: RuntimeFetchOptions) => this.call("PUT", init) - public readonly POST = (init?: RuntimeFetchOptions) => this.call("POST", init) - public readonly DELETE = (init?: RuntimeFetchOptions) => this.call("DELETE", init) - public readonly OPTIONS = (init?: RuntimeFetchOptions) => this.call("OPTIONS", init) - public readonly HEAD = (init?: RuntimeFetchOptions) => this.call("HEAD", init) - public readonly PATCH = (init?: RuntimeFetchOptions) => this.call("PATCH", init) - public readonly TRACE = (init?: RuntimeFetchOptions) => this.call("TRACE", init) -} - -export const wrapAsPathBasedClient = < - Paths extends Record, - Media extends MediaType = MediaType ->( - client: Client -): PathBasedClient => { - const cache = new Map() - const target = asStrictApiClient>({}) - - return new Proxy(target, { - get: (_target, property) => { - if (typeof property !== "string") { - return - } - - const cached = cache.get(property) - if (cached !== undefined) { - return cached - } - - const forwarder = new PathCallForwarder(asStrictApiClient(client), property) - cache.set(property, forwarder) - return forwarder - } - }) -} - -export const createPathBasedClient = < - Paths extends Record, - Media extends MediaType = MediaType ->( - clientOptions?: ClientOptions -): PathBasedClient => wrapAsPathBasedClient(createClient(clientOptions)) - export const createClientEffect = ( clientOptions?: ClientOptions ): ClientEffect => asStrictApiClient>(createRuntimeEffectClient(clientOptions)) - -export const registerDefaultDispatchers = ( - _dispatchers: DispatchersFor -): void => {} diff --git a/packages/app/src/shell/api-client/index.ts b/packages/app/src/shell/api-client/index.ts index d9e9c01..45aff24 100644 --- a/packages/app/src/shell/api-client/index.ts +++ b/packages/app/src/shell/api-client/index.ts @@ -1,67 +1,35 @@ -// CHANGE: Main entry point for api-client shell module -// WHY: Export public API with clear separation of concerns -// QUOTE(ТЗ): "Публичный API должен иметь вид: strictClient.GET(path, options): Effect, BoundaryError, never>" -// REF: issue-2, section 6 +// CHANGE: Main entry point for the openapi-effect shell module +// WHY: Export only the Effect client API and its openapi-fetch-compatible input helpers +// QUOTE(ТЗ): "итоговый тип возвращался от effect а не promise" +// REF: user-msg-openapi-effect-only // SOURCE: n/a // PURITY: SHELL (re-exports) // COMPLEXITY: O(1) -// Shell types and functions (runtime) export type { - Decoder, - Dispatcher, - RawResponse, - RequestOptions, - StrictClient, - StrictRequestInit -} from "./strict-client.js" - -export { - createDispatcher, - createStrictClient, - createUniversalDispatcher, - executeRequest, - parseJSON, - unexpectedContentType, - unexpectedStatus -} from "./strict-client.js" - -// High-level client creation API -export type { - Client, ClientEffect, - ClientForPath, ClientOptions, - DispatchersFor, EffectClient, EffectClientMethod, EffectClientRequestMethod, FetchOptions, - FetchResponse, HeadersOptions, Middleware, ParseAs, - PathBasedClient, QuerySerializer, QuerySerializerOptions, RequestBodyOption, - RequestOptions as FetchRequestOptions, - StrictApiClient, - StrictApiClientWithDispatchers + RequestOptions } from "./create-client.js" export { - createClient, createClientEffect, createFinalURL, - createPathBasedClient, createQuerySerializer, defaultBodySerializer, defaultPathSerializer, mergeHeaders, - registerDefaultDispatchers, removeTrailingSlash, serializeArrayParam, serializeObjectParam, - serializePrimitiveParam, - wrapAsPathBasedClient + serializePrimitiveParam } from "./create-client.js" diff --git a/packages/app/src/shell/api-client/strict-client.ts b/packages/app/src/shell/api-client/strict-client.ts deleted file mode 100644 index c821232..0000000 --- a/packages/app/src/shell/api-client/strict-client.ts +++ /dev/null @@ -1,471 +0,0 @@ -// CHANGE: Implement Effect-based HTTP client with Effect-native error handling -// WHY: Force explicit handling of HTTP errors (4xx, 5xx) via Effect error channel -// QUOTE(ТЗ): "каждый запрос возвращает Effect; Failure включает все инварианты протокола и схемы" -// REF: issue-2, section 2, 4, 5.1 -// SOURCE: n/a -// FORMAT THEOREM: ∀ req ∈ Requests: execute(req) → Effect -// PURITY: SHELL -// EFFECT: Effect, ApiFailure, HttpClient.HttpClient> -// INVARIANT: 2xx → success channel, non-2xx → error channel (forced handling) -// COMPLEXITY: O(1) per request / O(n) for body size - -import * as HttpBody from "@effect/platform/HttpBody" -import * as HttpClient from "@effect/platform/HttpClient" -import * as HttpClientRequest from "@effect/platform/HttpClientRequest" -import { Effect } from "effect" -import type { HttpMethod } from "openapi-typescript-helpers" - -import type { - ApiFailure, - ApiSuccess, - DecodeError, - OperationFor, - ParseError, - ResponsesFor, - TransportError, - UnexpectedContentType, - UnexpectedStatus -} from "../../core/api-client/strict-types.js" -import { - asDispatcher, - asJson, - asRawResponse, - asStrictRequestInit, - type ClassifyFn, - type Dispatcher, - type Json, - type RawResponse -} from "../../core/axioms.js" - -// Re-export Dispatcher type for consumers - -/** - * Decoder for response body - * - * @pure false - may perform validation - * @effect Effect - */ -export type Decoder = ( - status: number, - contentType: string, - body: string -) => Effect.Effect - -/** - * Configuration for a strict API client request - */ -export type StrictRequestInit = { - readonly method: HttpMethod - readonly url: string - readonly dispatcher: Dispatcher - readonly headers?: HeadersInit - readonly body?: BodyInit - readonly signal?: AbortSignal -} - -/** - * Execute HTTP request with Effect-native error handling - * - * @param config - Request configuration with dispatcher - * @returns Effect with success (2xx) and failures (non-2xx + boundary errors) - * - * **Effect Channel Design:** - * - Success channel: `ApiSuccess` - 2xx responses only - * - Error channel: `ApiFailure` - HTTP errors (4xx, 5xx) + boundary errors - * - * This forces developers to explicitly handle HTTP errors using: - * - `Effect.catchTag` for specific error types - * - `Effect.match` for exhaustive handling - * - `Effect.catchAll` for generic error handling - * - * @pure false - performs HTTP request - * @effect Effect, ApiFailure, HttpClient.HttpClient> - * @invariant 2xx → success channel, non-2xx → error channel - * @precondition config.dispatcher handles all schema statuses - * @postcondition ∀ response: success(2xx) ∨ httpError(non-2xx) ∨ boundaryError - * @complexity O(1) + O(|body|) for text extraction - */ -export const executeRequest = ( - config: StrictRequestInit -): Effect.Effect, ApiFailure, HttpClient.HttpClient> => - Effect.gen(function*() { - // STEP 1: Get HTTP client from context - const client = yield* HttpClient.HttpClient - - // STEP 2: Build request based on method - const request = buildRequest(config) - - // STEP 3: Execute request with error mapping - const rawResponse = yield* Effect.mapError( - Effect.gen(function*() { - const response = yield* client.execute(request) - const text = yield* response.text - return asRawResponse({ - status: response.status, - headers: toNativeHeaders(response.headers), - text - }) - }), - (error): TransportError => ({ - _tag: "TransportError", - error: error instanceof Error ? error : new Error(String(error)) - }) - ) - - // STEP 4: Delegate classification to dispatcher (handles status/content-type/decode) - return yield* config.dispatcher(rawResponse) - }) - -/** - * Build HTTP request from config - * - * @pure true - */ -const buildRequest = (config: StrictRequestInit): HttpClientRequest.HttpClientRequest => { - const methodMap: Record HttpClientRequest.HttpClientRequest> = { - get: HttpClientRequest.get, - post: HttpClientRequest.post, - put: HttpClientRequest.put, - patch: HttpClientRequest.patch, - delete: HttpClientRequest.del, - head: HttpClientRequest.head, - options: HttpClientRequest.options - } - - const createRequest = methodMap[config.method] ?? HttpClientRequest.get - let request = createRequest(config.url) - - // Add headers if provided - if (config.headers !== undefined) { - const headers = toRecordHeaders(config.headers) - request = HttpClientRequest.setHeaders(request, headers) - } - - // Add body if provided - if (config.body !== undefined) { - const bodyText = typeof config.body === "string" ? config.body : JSON.stringify(config.body) - request = HttpClientRequest.setBody(request, HttpBody.text(bodyText)) - } - - return request -} - -/** - * Convert Headers to Record - * - * @pure true - */ -const toRecordHeaders = (headers: HeadersInit): Record => { - if (headers instanceof Headers) { - const result: Record = {} - for (const [key, value] of headers.entries()) { - result[key] = value - } - return result - } - if (Array.isArray(headers)) { - const result: Record = {} - for (const headerPair of headers) { - const [headerKey, headerValue] = headerPair - result[headerKey] = headerValue - } - return result - } - return headers -} - -/** - * Convert @effect/platform Headers to native Headers - * - * @pure true - */ -const toNativeHeaders = (platformHeaders: { readonly [key: string]: string }): Headers => { - const headers = new Headers() - for (const [key, value] of Object.entries(platformHeaders)) { - headers.set(key, value) - } - return headers -} - -/** - * Helper to create dispatcher from switch-based classifier - * - * This function uses a permissive type signature to allow generated code - * to work with any response variant without requiring exact type matching. - * The classify function can return any Effect with union types for success/error. - * - * NOTE: Uses axioms module for type casts to allow heterogeneous Effect - * unions from switch statements. The returned Dispatcher is properly typed. - * - * @pure true - returns pure function - * @complexity O(1) - */ - -export const createDispatcher = ( - classify: ClassifyFn -): Dispatcher => { - return asDispatcher((response: RawResponse) => { - const contentType = response.headers.get("content-type") ?? undefined - return classify(response.status, contentType, response.text) - }) -} - -/** - * Helper to parse JSON with error handling - * - * @pure false - performs parsing - * @effect Effect - */ -export const parseJSON = ( - status: number, - contentType: string, - text: string -): Effect.Effect => - Effect.try({ - try: () => asJson(JSON.parse(text)), - catch: (error): ParseError => ({ - _tag: "ParseError", - status, - contentType, - error: error instanceof Error ? error : new Error(String(error)), - body: text - }) - }) - -/** - * Helper to create UnexpectedStatus error - * - * @pure true - */ -export const unexpectedStatus = (status: number, body: string): UnexpectedStatus => ({ - _tag: "UnexpectedStatus", - status, - body -}) - -/** - * Helper to create UnexpectedContentType error - * - * @pure true - */ -export const unexpectedContentType = ( - status: number, - expected: ReadonlyArray, - actual: string | undefined, - body: string -): UnexpectedContentType => ({ - _tag: "UnexpectedContentType", - status, - expected, - actual, - body -}) - -/** - * Generic client interface for any OpenAPI schema with Effect-native error handling - * - * **Effect Channel Design:** - * - Success channel: `ApiSuccess` - 2xx responses - * - Error channel: `ApiFailure` - HTTP errors (4xx, 5xx) + boundary errors - * - * @pure false - performs HTTP requests - * @effect Effect, ApiFailure, HttpClient.HttpClient> - */ -export type StrictClient = { - readonly GET: ( - path: Path, - options: RequestOptions - ) => Effect.Effect< - ApiSuccess>>, - ApiFailure>>, - HttpClient.HttpClient - > - - readonly POST: ( - path: Path, - options: RequestOptions - ) => Effect.Effect< - ApiSuccess>>, - ApiFailure>>, - HttpClient.HttpClient - > - - readonly PUT: ( - path: Path, - options: RequestOptions - ) => Effect.Effect< - ApiSuccess>>, - ApiFailure>>, - HttpClient.HttpClient - > - - readonly PATCH: ( - path: Path, - options: RequestOptions - ) => Effect.Effect< - ApiSuccess>>, - ApiFailure>>, - HttpClient.HttpClient - > - - readonly DELETE: ( - path: Path, - options: RequestOptions - ) => Effect.Effect< - ApiSuccess>>, - ApiFailure>>, - HttpClient.HttpClient - > -} - -/** - * Request options for a specific operation - */ -export type RequestOptions< - Paths extends object, - Path extends keyof Paths, - Method extends HttpMethod -> = { - readonly dispatcher: Dispatcher>> - readonly baseUrl: string - readonly params?: Record - readonly query?: Record - readonly headers?: HeadersInit - readonly body?: BodyInit - readonly signal?: AbortSignal -} - -/** - * Create a strict client for an OpenAPI schema - * - * @pure true - returns pure client object - * @complexity O(1) - */ -export const createStrictClient = (): StrictClient< - Paths -> => { - const makeRequest = ( - method: Method, - path: Path, - options: RequestOptions - ) => { - let url = `${options.baseUrl}${String(path)}` - - // Replace path parameters - if (options.params !== undefined) { - for (const [key, value] of Object.entries(options.params)) { - url = url.replace(`{${key}}`, encodeURIComponent(String(value))) - } - } - - // Add query parameters - if (options.query !== undefined) { - const params = new URLSearchParams() - for (const [key, value] of Object.entries(options.query)) { - params.append(key, String(value)) - } - url = `${url}?${params.toString()}` - } - - // Build config object, only including optional properties if they are defined - // This satisfies exactOptionalPropertyTypes constraint - const config = asStrictRequestInit>>>({ - method, - url, - dispatcher: options.dispatcher, - ...(options.headers !== undefined && { headers: options.headers }), - ...(options.body !== undefined && { body: options.body }), - ...(options.signal !== undefined && { signal: options.signal }) - }) - - return executeRequest(config) - } - - return { - GET: (path, options) => makeRequest("get", path, options), - POST: (path, options) => makeRequest("post", path, options), - PUT: (path, options) => makeRequest("put", path, options), - PATCH: (path, options) => makeRequest("patch", path, options), - DELETE: (path, options) => makeRequest("delete", path, options) - } satisfies StrictClient -} - -// CHANGE: Add universal dispatcher that handles any OpenAPI responses generically -// WHY: Enable createClient(options) without code generation or manual dispatcher wiring -// QUOTE(ТЗ): "Я не хочу создавать какие-то дополнительные модули" -// REF: issue-5 -// SOURCE: n/a -// FORMAT THEOREM: ∀ status, ct: universalDispatcher(status, ct, text) → success(2xx) ∨ httpError(non-2xx) ∨ boundaryError -// PURITY: SHELL -// EFFECT: Effect, Exclude, TransportError>, never> -// INVARIANT: 2xx → success channel, non-2xx → error channel, no-content → body: undefined -// COMPLEXITY: O(1) per dispatch + O(|text|) for JSON parsing - -/** - * Create a universal dispatcher that handles any OpenAPI response generically - * - * The universal dispatcher classifies responses by status code range: - * - 2xx → success channel (ApiSuccess) - * - non-2xx → error channel (HttpError) - * - * For JSON content types, it parses the body. For no-content responses (empty body), - * it returns undefined body with contentType "none". - * - * This enables using createClient(options) without generating - * per-operation dispatchers, fulfilling the zero-boilerplate DSL requirement. - * - * @pure true - returns pure dispatcher function - * @complexity O(1) creation + O(|body|) per dispatch - */ -export const createUniversalDispatcher = (): Dispatcher => { - return asDispatcher((response: RawResponse) => { - const contentType = response.headers.get("content-type") ?? undefined - const is2xx = response.status >= 200 && response.status < 300 - - // No-content response (empty body or 204) - if (response.text === "" || response.status === 204) { - const variant = { - status: response.status, - contentType: "none" as const, - body: undefined - } as const - - return is2xx - ? Effect.succeed(variant) - : Effect.fail({ - _tag: "HttpError" as const, - ...variant - }) - } - - // JSON content type - if (contentType?.includes("application/json")) { - return Effect.gen(function*() { - const parsed = yield* parseJSON(response.status, "application/json", response.text) - const variant = { - status: response.status, - contentType: "application/json" as const, - body: parsed - } as const - - if (is2xx) { - return variant - } - return yield* Effect.fail({ - _tag: "HttpError" as const, - ...variant - }) - }) - } - - // Unknown content type - return Effect.fail(unexpectedContentType( - response.status, - ["application/json"], - contentType, - response.text - )) - }) -} - -export { type Dispatcher, type RawResponse } from "../../core/axioms.js" diff --git a/packages/app/src/shell/cli.ts b/packages/app/src/shell/cli.ts deleted file mode 100644 index cd9edea..0000000 --- a/packages/app/src/shell/cli.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as S from "@effect/schema/Schema" -import { Effect, pipe } from "effect" - -import type { GreetingVariant } from "../core/greeting.js" - -const cliSchema = S.Struct({ - name: S.optionalWith(S.NonEmptyString, { default: () => "Effect" }) -}) - -type CliInput = S.Schema.Type - -const toVariant = (input: CliInput): GreetingVariant => - input.name.toLowerCase() === "effect" - ? { kind: "effect" } - : { kind: "named", name: input.name } - -export const readGreetingVariant = pipe( - Effect.sync(() => process.argv.slice(2)), - Effect.map((args) => args.length > 0 && args[0] !== undefined ? { name: args[0] } : {}), - Effect.flatMap(S.decodeUnknown(cliSchema)), - Effect.map((input) => toVariant(input)) -) diff --git a/packages/app/tests/api-client/boundary-errors.test.ts b/packages/app/tests/api-client/boundary-errors.test.ts deleted file mode 100644 index 655d0b5..0000000 --- a/packages/app/tests/api-client/boundary-errors.test.ts +++ /dev/null @@ -1,236 +0,0 @@ -// CHANGE: Unit tests for boundary error cases (C1-C4 from acceptance criteria) -// WHY: Verify all protocol/parsing failures are correctly classified -// REF: issue-2, section C - -import * as HttpClient from "@effect/platform/HttpClient" -import * as HttpClientError from "@effect/platform/HttpClientError" -import * as HttpClientResponse from "@effect/platform/HttpClientResponse" -import { Effect, Either, Layer, Match } from "effect" -import { describe, expect, it } from "vitest" - -import { - createDispatcher, - executeRequest, - parseJSON, - unexpectedContentType, - unexpectedStatus -} from "../../src/shell/api-client/strict-client.js" - -type Json = null | boolean | number | string | ReadonlyArray | { readonly [k: string]: Json } - -const createMockHttpClientLayer = ( - status: number, - headers: Record, - body: string -): Layer.Layer => - Layer.succeed( - HttpClient.HttpClient, - HttpClient.make((request) => - Effect.succeed( - HttpClientResponse.fromWeb(request, new Response(body, { status, headers: new Headers(headers) })) - ) - ) - ) - -const createFailingHttpClientLayer = (error: Error): Layer.Layer => - Layer.succeed( - HttpClient.HttpClient, - HttpClient.make((request) => - Effect.fail(new HttpClientError.RequestError({ request, reason: "Transport", cause: error })) - ) - ) - -describe("C1: UnexpectedStatus", () => { - it("should return UnexpectedStatus for status not in schema (418)", () => - Effect.gen(function*() { - const dispatcher = createDispatcher((status, _contentType, text) => - Match.value(status).pipe( - Match.when( - 200, - () => Effect.succeed({ status: 200, contentType: "application/json" as const, body: {} } as const) - ), - Match.when( - 500, - () => Effect.succeed({ status: 500, contentType: "application/json" as const, body: {} } as const) - ), - Match.orElse(() => Effect.fail(unexpectedStatus(status, text))) - ) - ) - - const result = yield* Effect.either( - executeRequest({ method: "get", url: "https://api.example.com/test", dispatcher }).pipe( - Effect.provide(createMockHttpClientLayer(418, { "content-type": "text/plain" }, "I'm a teapot")) - ) - ) - - expect(Either.isLeft(result)).toBe(true) - if (Either.isLeft(result)) { - expect(result.left).toMatchObject({ _tag: "UnexpectedStatus", status: 418, body: "I'm a teapot" }) - } - }).pipe(Effect.runPromise)) -}) - -describe("C2: UnexpectedContentType", () => { - it("should return UnexpectedContentType for 200 with text/html", () => - Effect.gen(function*() { - const dispatcher = createDispatcher((status, contentType, text) => - Match.value(status).pipe( - Match.when(200, () => - contentType?.includes("application/json") - ? Effect.succeed({ status: 200, contentType: "application/json" as const, body: {} } as const) - : Effect.fail(unexpectedContentType(status, ["application/json"], contentType, text))), - Match.orElse(() => Effect.fail(unexpectedStatus(status, text))) - ) - ) - - const result = yield* Effect.either( - executeRequest({ method: "get", url: "https://api.example.com/test", dispatcher }).pipe( - Effect.provide( - createMockHttpClientLayer(200, { "content-type": "text/html" }, "Hello") - ) - ) - ) - - expect(Either.isLeft(result)).toBe(true) - if (Either.isLeft(result)) { - expect(result.left).toMatchObject({ - _tag: "UnexpectedContentType", - status: 200, - expected: ["application/json"], - actual: "text/html" - }) - } - }).pipe(Effect.runPromise)) -}) - -describe("C3: ParseError", () => { - it("should return ParseError for malformed JSON", () => - Effect.gen(function*() { - const malformedJSON = "{\"bad\": json}" - const result = yield* Effect.either(parseJSON(200, "application/json", malformedJSON)) - - expect(Either.isLeft(result)).toBe(true) - if (Either.isLeft(result)) { - expect(result.left).toMatchObject({ _tag: "ParseError", status: 200, contentType: "application/json" }) - expect(result.left.error).toBeInstanceOf(Error) - } - }).pipe(Effect.runPromise)) - - it("should return ParseError for incomplete JSON", () => - Effect.gen(function*() { - const result = yield* Effect.either(parseJSON(200, "application/json", "{\"key\": \"value\"")) - expect(Either.isLeft(result)).toBe(true) - if (Either.isLeft(result)) expect(result.left._tag).toBe("ParseError") - }).pipe(Effect.runPromise)) - - it("should succeed for valid JSON", () => - Effect.gen(function*() { - const result = yield* Effect.either(parseJSON(200, "application/json", "{\"key\": \"value\"}")) - expect(Either.isRight(result)).toBe(true) - if (Either.isRight(result)) expect(result.right).toEqual({ key: "value" }) - }).pipe(Effect.runPromise)) -}) - -describe("C4: DecodeError", () => { - it("should return DecodeError when decoded value fails schema", () => - Effect.gen(function*() { - const validJSONWrongSchema = "{\"unexpected\": \"field\"}" - const mockDecoder = (status: number, contentType: string, body: string, parsed: Json) => - typeof parsed === "object" && parsed !== null && "id" in parsed && "name" in parsed - ? Effect.succeed(parsed) - : Effect.fail({ - _tag: "DecodeError" as const, - status, - contentType, - error: new Error("Expected id and name"), - body - }) - - const result = yield* Effect.either( - Effect.gen(function*() { - const parsed = yield* parseJSON(200, "application/json", validJSONWrongSchema) - return yield* mockDecoder(200, "application/json", validJSONWrongSchema, parsed) - }) - ) - - expect(Either.isLeft(result)).toBe(true) - if (Either.isLeft(result)) { - expect(result.left).toMatchObject({ _tag: "DecodeError", status: 200, contentType: "application/json" }) - } - }).pipe(Effect.runPromise)) -}) - -describe("TransportError", () => { - it("should return TransportError on network failure", () => - Effect.gen(function*() { - const dispatcher = createDispatcher(() => - Effect.succeed({ status: 200, contentType: "application/json" as const, body: {} } as const) - ) - - const result = yield* Effect.either( - executeRequest({ method: "get", url: "https://api.example.com/test", dispatcher }).pipe( - Effect.provide(createFailingHttpClientLayer(new Error("Network connection failed"))) - ) - ) - - expect(Either.isLeft(result)).toBe(true) - if (Either.isLeft(result)) expect(result.left).toMatchObject({ _tag: "TransportError" }) - }).pipe(Effect.runPromise)) - - it("should return TransportError on abort", () => - Effect.gen(function*() { - const abortError = new Error("Request aborted") - abortError.name = "AbortError" - - const dispatcher = createDispatcher(() => - Effect.succeed({ status: 200, contentType: "application/json" as const, body: {} } as const) - ) - - const result = yield* Effect.either( - executeRequest({ method: "get", url: "https://api.example.com/test", dispatcher }).pipe( - Effect.provide(createFailingHttpClientLayer(abortError)) - ) - ) - - expect(Either.isLeft(result)).toBe(true) - if (Either.isLeft(result)) { - const err = result.left as { _tag?: string } - expect(err._tag).toBe("TransportError") - } - }).pipe(Effect.runPromise)) -}) - -describe("No uncaught exceptions", () => { - it("should never throw, only return Effect.fail", () => - Effect.gen(function*() { - const testCases = [ - { status: 418, body: "teapot", contentType: "application/json" }, - { status: 200, contentType: "text/html", body: "" }, - { status: 200, contentType: "application/json", body: "{bad json" }, - { status: 200, contentType: "application/json", body: "{\"valid\": \"json\"}" } - ] - - for (const testCase of testCases) { - const dispatcher = createDispatcher((status, contentType, text) => - Match.value(status === 200 && contentType?.includes("application/json")).pipe( - Match.when(true, () => - Effect.gen(function*() { - const parsed = yield* parseJSON(status, "application/json", text) - return { status: 200, contentType: "application/json" as const, body: parsed } as const - })), - Match.orElse(() => Effect.fail(unexpectedStatus(status, text))) - ) - ) - - const result = yield* Effect.either( - executeRequest({ method: "get", url: "https://api.example.com/test", dispatcher }).pipe( - Effect.provide( - createMockHttpClientLayer(testCase.status, { "content-type": testCase.contentType }, testCase.body) - ) - ) - ) - - expect(Either.isLeft(result) || Either.isRight(result)).toBe(true) - } - }).pipe(Effect.runPromise)) -}) diff --git a/packages/app/tests/api-client/create-client-dispatchers.test.ts b/packages/app/tests/api-client/create-client-dispatchers.test.ts deleted file mode 100644 index e37a360..0000000 --- a/packages/app/tests/api-client/create-client-dispatchers.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -// CHANGE: Add tests for createClient dispatcher-less usage with openapi-fetch envelope -// WHY: Verify createClient can be used without per-call dispatcher and keeps openapi-fetch response shape -// QUOTE(ТЗ): "openapi-effect должен почти 1 в 1 заменяться с openapi-fetch" -// REF: user-msg-2026-02-12 -// SOURCE: n/a -// PURITY: SHELL -// EFFECT: Effect -// INVARIANT: 2xx -> data, non-2xx -> error in success channel -// COMPLEXITY: O(1) per test - -import { Effect } from "effect" -import { describe, expect, it } from "vitest" - -import "../../src/generated/dispatchers-by-path.js" -import { createClient } from "../../src/shell/api-client/create-client.js" -import type { Paths } from "../fixtures/petstore.openapi.js" - -type PetstorePaths = Paths & object - -const createMockFetch = ( - status: number, - headers: Record, - body: string -) => -(_request: Request) => - Effect.runPromise( - Effect.succeed( - status === 204 || status === 304 - ? new Response(null, { status, headers: new Headers(headers) }) - : new Response(body, { status, headers: new Headers(headers) }) - ) - ) - -describe("createClient (auto-dispatching)", () => { - it("should handle 200 success without passing dispatcher", () => - Effect.gen(function*() { - const successBody = JSON.stringify([ - { id: "1", name: "Fluffy" }, - { id: "2", name: "Spot" } - ]) - - const client = createClient({ - baseUrl: "https://api.example.com", - fetch: createMockFetch(200, { "content-type": "application/json" }, successBody) - }) - - const result = yield* client.GET("/pets", { - params: { - query: { limit: 10 } - } - }) - - expect(result.response.status).toBe(200) - expect(Array.isArray(result.data)).toBe(true) - }).pipe(Effect.runPromise)) - - it("should keep schema 404 inside response envelope", () => - Effect.gen(function*() { - const errorBody = JSON.stringify({ code: 404, message: "Pet not found" }) - - const client = createClient({ - baseUrl: "https://api.example.com", - fetch: createMockFetch(404, { "content-type": "application/json" }, errorBody) - }) - - const result = yield* client.GET("/pets/{petId}", { - params: { - path: { petId: "999" } - } - }) - - expect(result.response.status).toBe(404) - expect(result.error).toMatchObject({ code: 404, message: "Pet not found" }) - }).pipe(Effect.runPromise)) -}) diff --git a/packages/app/tests/api-client/generated-dispatchers.get-delete.test.ts b/packages/app/tests/api-client/generated-dispatchers.get-delete.test.ts deleted file mode 100644 index 7c68738..0000000 --- a/packages/app/tests/api-client/generated-dispatchers.get-delete.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -// CHANGE: Split getPet/deletePet dispatcher tests into separate file -// WHY: Keep test files under lint max-lines while preserving coverage -// QUOTE(ТЗ): "TypeScript должен выдавать ошибку 'неполное покрытие' через паттерн assertNever" -// REF: issue-2, section A3, 4.2 -// SOURCE: n/a -// FORMAT THEOREM: ∀ op ∈ {getPet, deletePet}: success(2xx) | httpError(non-2xx) | boundaryError -// PURITY: SHELL -// EFFECT: Effect -// INVARIANT: 2xx → isRight (success), non-2xx → isLeft (HttpError), unexpected → isLeft (BoundaryError) -// COMPLEXITY: O(1) per test - -import * as HttpClient from "@effect/platform/HttpClient" -import * as HttpClientResponse from "@effect/platform/HttpClientResponse" -import { Effect, Either, Layer } from "effect" -import { describe, expect, it } from "vitest" - -import { dispatcherdeletePet, dispatchergetPet } from "../../src/generated/dispatch.js" -import { createStrictClient } from "../../src/shell/api-client/strict-client.js" -import type { Paths } from "../fixtures/petstore.openapi.js" - -type PetstorePaths = Paths & object - -/** - * Create a mock HttpClient layer that returns a fixed response - * Note: 204 and 304 responses cannot have a body per HTTP spec - * - * @pure true - returns pure layer - */ -const createMockHttpClientLayer = ( - status: number, - headers: Record, - body: string -): Layer.Layer => - Layer.succeed( - HttpClient.HttpClient, - HttpClient.make( - (request) => - Effect.succeed( - HttpClientResponse.fromWeb( - request, - // 204 and 304 responses cannot have a body - status === 204 || status === 304 - ? new Response(null, { status, headers: new Headers(headers) }) - : new Response(body, { status, headers: new Headers(headers) }) - ) - ) - ) - ) - -describe("Generated dispatcher: getPet", () => { - it("should handle 200 success with pet data", () => - Effect.gen(function*() { - const pet = JSON.stringify({ id: "42", name: "Buddy", tag: "dog" }) - - const client = createStrictClient() - - const result = yield* Effect.either( - client.GET("/pets/{petId}", { - baseUrl: "https://api.example.com", - dispatcher: dispatchergetPet, - params: { petId: "42" } - }).pipe( - Effect.provide( - createMockHttpClientLayer(200, { "content-type": "application/json" }, pet) - ) - ) - ) - - expect(Either.isRight(result)).toBe(true) - if (Either.isRight(result)) { - expect(result.right.status).toBe(200) - } - }).pipe(Effect.runPromise)) - - it("should return HttpError for 404 not found (error channel)", () => - Effect.gen(function*() { - const errorBody = JSON.stringify({ code: 404, message: "Pet not found" }) - - const client = createStrictClient() - - const result = yield* Effect.either( - client.GET("/pets/{petId}", { - baseUrl: "https://api.example.com", - dispatcher: dispatchergetPet, - params: { petId: "999" } - }).pipe( - Effect.provide( - createMockHttpClientLayer(404, { "content-type": "application/json" }, errorBody) - ) - ) - ) - - // 404 is in schema → HttpError in error channel (forces explicit handling) - expect(Either.isLeft(result)).toBe(true) - if (Either.isLeft(result)) { - expect(result.left).toMatchObject({ - _tag: "HttpError", - status: 404 - }) - } - }).pipe(Effect.runPromise)) -}) - -describe("Generated dispatcher: deletePet", () => { - it("should handle 204 no content", () => - Effect.gen(function*() { - const client = createStrictClient() - - const result = yield* Effect.either( - client.DELETE("/pets/{petId}", { - baseUrl: "https://api.example.com", - dispatcher: dispatcherdeletePet, - params: { petId: "42" } - }).pipe( - Effect.provide( - createMockHttpClientLayer(204, {}, "") - ) - ) - ) - - expect(Either.isRight(result)).toBe(true) - if (Either.isRight(result)) { - expect(result.right.status).toBe(204) - expect(result.right.contentType).toBe("none") - expect(result.right.body).toBeUndefined() - } - }).pipe(Effect.runPromise)) - - it("should return HttpError for 404 pet not found (error channel)", () => - Effect.gen(function*() { - const errorBody = JSON.stringify({ code: 404, message: "Pet not found" }) - - const client = createStrictClient() - - const result = yield* Effect.either( - client.DELETE("/pets/{petId}", { - baseUrl: "https://api.example.com", - dispatcher: dispatcherdeletePet, - params: { petId: "999" } - }).pipe( - Effect.provide( - createMockHttpClientLayer(404, { "content-type": "application/json" }, errorBody) - ) - ) - ) - - // 404 is in schema → HttpError in error channel (forces explicit handling) - expect(Either.isLeft(result)).toBe(true) - if (Either.isLeft(result)) { - expect(result.left).toMatchObject({ - _tag: "HttpError", - status: 404 - }) - } - }).pipe(Effect.runPromise)) -}) diff --git a/packages/app/tests/api-client/generated-dispatchers.test.ts b/packages/app/tests/api-client/generated-dispatchers.test.ts deleted file mode 100644 index a5a693a..0000000 --- a/packages/app/tests/api-client/generated-dispatchers.test.ts +++ /dev/null @@ -1,243 +0,0 @@ -// CHANGE: Tests for generated dispatchers with Effect-native error handling -// WHY: Verify 2xx → success channel, non-2xx → error channel (forced handling) -// QUOTE(ТЗ): "TypeScript должен выдавать ошибку 'неполное покрытие' через паттерн assertNever" -// REF: issue-2, section A3, 4.2 -// SOURCE: n/a -// FORMAT THEOREM: ∀ op ∈ GeneratedOps: success(2xx) | httpError(non-2xx) | boundaryError -// PURITY: SHELL -// EFFECT: Effect -// INVARIANT: 2xx → isRight (success), non-2xx → isLeft (HttpError), unexpected → isLeft (BoundaryError) -// COMPLEXITY: O(1) per test - -import * as HttpClient from "@effect/platform/HttpClient" -import * as HttpClientResponse from "@effect/platform/HttpClientResponse" -import { Effect, Either, Layer } from "effect" -import { describe, expect, it } from "vitest" - -import { dispatchercreatePet, dispatcherlistPets } from "../../src/generated/dispatch.js" -import { createStrictClient } from "../../src/shell/api-client/strict-client.js" -import type { Paths } from "../fixtures/petstore.openapi.js" - -type PetstorePaths = Paths & object - -/** - * Create a mock HttpClient layer that returns a fixed response - * Note: 204 and 304 responses cannot have a body per HTTP spec - * - * @pure true - returns pure layer - */ -const createMockHttpClientLayer = ( - status: number, - headers: Record, - body: string -): Layer.Layer => - Layer.succeed( - HttpClient.HttpClient, - HttpClient.make( - (request) => - Effect.succeed( - HttpClientResponse.fromWeb( - request, - // 204 and 304 responses cannot have a body - status === 204 || status === 304 - ? new Response(null, { status, headers: new Headers(headers) }) - : new Response(body, { status, headers: new Headers(headers) }) - ) - ) - ) - ) - -describe("Generated dispatcher: listPets", () => { - it("should handle 200 success response", () => - Effect.gen(function*() { - const successBody = JSON.stringify([ - { id: "1", name: "Fluffy" }, - { id: "2", name: "Spot" } - ]) - - const client = createStrictClient() - - const result = yield* Effect.either( - client.GET("/pets", { - baseUrl: "https://api.example.com", - dispatcher: dispatcherlistPets - }).pipe( - Effect.provide( - createMockHttpClientLayer(200, { "content-type": "application/json" }, successBody) - ) - ) - ) - - expect(Either.isRight(result)).toBe(true) - if (Either.isRight(result)) { - expect(result.right.status).toBe(200) - expect(result.right.contentType).toBe("application/json") - expect(Array.isArray(result.right.body)).toBe(true) - } - }).pipe(Effect.runPromise)) - - it("should return HttpError for 500 response (error channel)", () => - Effect.gen(function*() { - const errorBody = JSON.stringify({ code: 500, message: "Internal server error" }) - - const client = createStrictClient() - - const result = yield* Effect.either( - client.GET("/pets", { - baseUrl: "https://api.example.com", - dispatcher: dispatcherlistPets - }).pipe( - Effect.provide( - createMockHttpClientLayer(500, { "content-type": "application/json" }, errorBody) - ) - ) - ) - - // 500 is in schema → HttpError in error channel (forces explicit handling) - expect(Either.isLeft(result)).toBe(true) - if (Either.isLeft(result)) { - expect(result.left).toMatchObject({ - _tag: "HttpError", - status: 500, - contentType: "application/json" - }) - } - }).pipe(Effect.runPromise)) - - it("should return UnexpectedStatus for 404 (not in schema)", () => - Effect.gen(function*() { - const client = createStrictClient() - - const result = yield* Effect.either( - client.GET("/pets", { - baseUrl: "https://api.example.com", - dispatcher: dispatcherlistPets - }).pipe( - Effect.provide( - createMockHttpClientLayer( - 404, - { "content-type": "application/json" }, - JSON.stringify({ message: "Not found" }) - ) - ) - ) - ) - - expect(Either.isLeft(result)).toBe(true) - if (Either.isLeft(result)) { - expect(result.left).toMatchObject({ - _tag: "UnexpectedStatus", - status: 404 - }) - } - }).pipe(Effect.runPromise)) -}) - -describe("Generated dispatcher: createPet", () => { - it("should handle 201 created response", () => - Effect.gen(function*() { - const createdPet = JSON.stringify({ id: "123", name: "Rex" }) - - const client = createStrictClient() - - const result = yield* Effect.either( - client.POST("/pets", { - baseUrl: "https://api.example.com", - dispatcher: dispatchercreatePet, - body: JSON.stringify({ name: "Rex" }) - }).pipe( - Effect.provide( - createMockHttpClientLayer(201, { "content-type": "application/json" }, createdPet) - ) - ) - ) - - expect(Either.isRight(result)).toBe(true) - if (Either.isRight(result)) { - expect(result.right.status).toBe(201) - expect(result.right.contentType).toBe("application/json") - } - }).pipe(Effect.runPromise)) - - it("should return HttpError for 400 validation error (error channel)", () => - Effect.gen(function*() { - const errorBody = JSON.stringify({ code: 400, message: "Validation failed" }) - - const client = createStrictClient() - - const result = yield* Effect.either( - client.POST("/pets", { - baseUrl: "https://api.example.com", - dispatcher: dispatchercreatePet, - body: JSON.stringify({ name: "" }) - }).pipe( - Effect.provide( - createMockHttpClientLayer(400, { "content-type": "application/json" }, errorBody) - ) - ) - ) - - // 400 is in schema → HttpError in error channel (forces explicit handling) - expect(Either.isLeft(result)).toBe(true) - if (Either.isLeft(result)) { - expect(result.left).toMatchObject({ - _tag: "HttpError", - status: 400 - }) - } - }).pipe(Effect.runPromise)) - - it("should return HttpError for 500 error (error channel)", () => - Effect.gen(function*() { - const errorBody = JSON.stringify({ code: 500, message: "Server error" }) - - const client = createStrictClient() - - const result = yield* Effect.either( - client.POST("/pets", { - baseUrl: "https://api.example.com", - dispatcher: dispatchercreatePet, - body: JSON.stringify({ name: "Test" }) - }).pipe( - Effect.provide( - createMockHttpClientLayer(500, { "content-type": "application/json" }, errorBody) - ) - ) - ) - - // 500 is in schema → HttpError in error channel (forces explicit handling) - expect(Either.isLeft(result)).toBe(true) - if (Either.isLeft(result)) { - expect(result.left).toMatchObject({ - _tag: "HttpError", - status: 500 - }) - } - }).pipe(Effect.runPromise)) -}) - -/** - * Exhaustiveness test: Verify TypeScript catches missing cases - * This is a compile-time test - uncomment to verify it fails typecheck - */ -describe("Exhaustiveness (compile-time verification)", () => { - it("demonstrates exhaustive pattern matching requirement", () => { - // This test documents the requirement but doesn't run - // In real code, omitting a status case should cause compile error - - /* - const handleResponse = (response: ApiResponse) => { - switch (response.status) { - case 200: - return "success" - // case 500: // <-- Commenting this out should cause compile error - // return "error" - default: - return assertNever(response) // <-- TypeScript error if not exhaustive - } - } - */ - - expect(true).toBe(true) // Placeholder - }) -}) diff --git a/packages/app/tests/app/main.test.ts b/packages/app/tests/app/main.test.ts deleted file mode 100644 index 5ccda80..0000000 --- a/packages/app/tests/app/main.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { describe, expect, it } from "@effect/vitest" -import { Effect } from "effect" -import { vi } from "vitest" - -import { program } from "../../src/app/program.js" - -const withLogSpy = Effect.acquireRelease( - Effect.sync(() => vi.spyOn(console, "log").mockImplementation(() => {})), - (spy) => - Effect.sync(() => { - spy.mockRestore() - }) -) - -const withArgv = (nextArgv: ReadonlyArray) => - Effect.acquireRelease( - Effect.sync(() => { - const previous = process.argv - process.argv = [...nextArgv] - return previous - }), - (previous) => - Effect.sync(() => { - process.argv = previous - }) - ) - -describe("main program", () => { - it.effect("logs default greeting when no args", () => - Effect.scoped( - Effect.gen(function*(_) { - const logSpy = yield* _(withLogSpy) - yield* _(withArgv(["node", "main"])) - yield* _(program) - yield* _(Effect.sync(() => { - expect(logSpy).toHaveBeenCalledTimes(1) - expect(logSpy).toHaveBeenLastCalledWith("Hello from Effect!") - })) - }) - )) - - it.effect("logs named greeting when name is provided", () => - Effect.scoped( - Effect.gen(function*(_) { - const logSpy = yield* _(withLogSpy) - yield* _(withArgv(["node", "main", "Alice"])) - yield* _(program) - yield* _(Effect.sync(() => { - expect(logSpy).toHaveBeenCalledTimes(1) - expect(logSpy).toHaveBeenLastCalledWith("Hello, Alice!") - })) - }) - )) -}) diff --git a/packages/app/tests/core/greeting.test.ts b/packages/app/tests/core/greeting.test.ts deleted file mode 100644 index e445167..0000000 --- a/packages/app/tests/core/greeting.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { describe, expect, it } from "@effect/vitest" -import { Effect } from "effect" - -import { formatGreeting, type GreetingVariant } from "../../src/core/greeting.js" - -describe("formatGreeting", () => { - it.effect("returns default Effect greeting for effect variant", () => - Effect.sync(() => { - const variant: GreetingVariant = { kind: "effect" } - const result = formatGreeting(variant) - expect(result).toBe("Hello from Effect!") - })) - - it.effect("formats deterministic greeting for named variant", () => - Effect.sync(() => { - const variant: GreetingVariant = { kind: "named", name: "Alice" } - const result = formatGreeting(variant) - expect(result).toBe("Hello, Alice!") - })) -}) diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json index bc4c415..9597d14 100644 --- a/packages/app/tsconfig.json +++ b/packages/app/tsconfig.json @@ -13,6 +13,7 @@ "include": [ "src/**/*", "tests/**/*", + "examples/strict-error-handling.ts", "vite.config.ts", "vitest.config.ts" ], diff --git a/packages/app/vite.config.ts b/packages/app/vite.config.ts index 249b519..51bd760 100644 --- a/packages/app/vite.config.ts +++ b/packages/app/vite.config.ts @@ -12,8 +12,8 @@ type Pkg = { peerDependencies?: Record | undefined } -// CHANGE: Build both the library entry (src/index.ts) and the CLI entry (src/app/main.ts). -// WHY: Consumers need a JS entrypoint in dist for `import "openapi-effect"`, while we keep the template CLI working. +// CHANGE: Build only the openapi-effect library entry. +// WHY: The package is a library, not a CLI application. // SOURCE: n/a const pkgPath = path.resolve(__dirname, "package.json") const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")) as Pkg @@ -37,8 +37,7 @@ export default defineConfig({ rollupOptions: { preserveEntrySignatures: "exports-only", input: { - index: path.resolve(__dirname, "src/index.ts"), - main: path.resolve(__dirname, "src/app/main.ts") + index: path.resolve(__dirname, "src/index.ts") }, external: isExternal, output: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b883aa..c86ae57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,51 +17,12 @@ importers: packages/app: dependencies: - '@effect/cli': - specifier: ^0.73.2 - version: 0.73.2(@effect/platform@0.94.5(effect@3.19.18))(@effect/printer-ansi@0.47.0(@effect/typeclass@0.38.0(effect@3.19.18))(effect@3.19.18))(@effect/printer@0.47.0(@effect/typeclass@0.38.0(effect@3.19.18))(effect@3.19.18))(effect@3.19.18) - '@effect/cluster': - specifier: ^0.56.4 - version: 0.56.4(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(effect@3.19.18) - '@effect/experimental': - specifier: ^0.58.0 - version: 0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18) - '@effect/platform': - specifier: ^0.94.5 - version: 0.94.5(effect@3.19.18) - '@effect/platform-node': - specifier: ^0.104.1 - version: 0.104.1(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18) - '@effect/printer': - specifier: ^0.47.0 - version: 0.47.0(@effect/typeclass@0.38.0(effect@3.19.18))(effect@3.19.18) - '@effect/printer-ansi': - specifier: ^0.47.0 - version: 0.47.0(@effect/typeclass@0.38.0(effect@3.19.18))(effect@3.19.18) - '@effect/rpc': - specifier: ^0.73.2 - version: 0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18) - '@effect/schema': - specifier: ^0.75.5 - version: 0.75.5(effect@3.19.18) - '@effect/sql': - specifier: ^0.49.0 - version: 0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18) - '@effect/typeclass': - specifier: ^0.38.0 - version: 0.38.0(effect@3.19.18) - '@effect/workflow': - specifier: ^0.16.0 - version: 0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18) effect: specifier: ^3.19.18 version: 3.19.18 openapi-typescript-helpers: specifier: ^0.1.0 version: 0.1.0 - ts-morph: - specifier: ^27.0.2 - version: 27.0.2 devDependencies: '@biomejs/biome': specifier: ^2.4.4 @@ -72,9 +33,6 @@ importers: '@effect/language-service': specifier: latest version: 0.75.1 - '@effect/vitest': - specifier: ^0.27.0 - version: 0.27.0(effect@3.19.18)(vitest@4.0.18(@types/node@24.10.13)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) '@eslint-community/eslint-plugin-eslint-comments': specifier: ^4.6.0 version: 4.6.0(eslint@10.0.1(jiti@2.6.1)) @@ -87,9 +45,6 @@ importers: '@eslint/js': specifier: 10.0.1 version: 10.0.1(eslint@10.0.1(jiti@2.6.1)) - '@prover-coder-ai/eslint-plugin-suggest-members': - specifier: ^0.0.25 - version: 0.0.25(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(eslint@10.0.1(jiti@2.6.1))(typescript@5.9.3) '@ton-ai-core/vibecode-linter': specifier: ^1.0.11 version: 1.0.11 @@ -359,116 +314,13 @@ packages: '@dprint/typescript@0.91.8': resolution: {integrity: sha512-tuKn4leCPItox1O4uunHcQF0QllDCvPWklnNQIh2PiWWVtRAGltJJnM4Cwj5AciplosD1Hiz7vAY3ew3crLb3A==} - '@effect/cli@0.73.2': - resolution: {integrity: sha512-K8IJo81+qa1LU8dhxcDU4QO/bIjL/dPd3zUOSCpLiuUNz8Y3/T+WNs3GqIXEhMfCFMSlRZERN0YgmtRlEZUREA==} - peerDependencies: - '@effect/platform': ^0.94.3 - '@effect/printer': ^0.47.0 - '@effect/printer-ansi': ^0.47.0 - effect: ^3.19.16 - - '@effect/cluster@0.56.4': - resolution: {integrity: sha512-7Je5/JlbZOlsSxsbKjr97dJed2cNGWsb+TLNgMcr5mRDbcWlFOTUGvsrisEJV6waosYLIg+2omPdvnvRoYKdhA==} - peerDependencies: - '@effect/platform': ^0.94.5 - '@effect/rpc': ^0.73.1 - '@effect/sql': ^0.49.0 - '@effect/workflow': ^0.16.0 - effect: ^3.19.17 - '@effect/eslint-plugin@0.3.2': resolution: {integrity: sha512-c4Vs9t3r54A4Zpl+wo8+PGzZz3JWYsip41H+UrebRLjQ2Hk/ap63IeCgN/HWcYtxtyhRopjp7gW9nOQ2Snbl+g==} - '@effect/experimental@0.58.0': - resolution: {integrity: sha512-IEP9sapjF6rFy5TkoqDPc86st/fnqUfjT7Xa3pWJrFGr1hzaMXHo+mWsYOZS9LAOVKnpHuVziDK97EP5qsCHVA==} - peerDependencies: - '@effect/platform': ^0.94.0 - effect: ^3.19.13 - ioredis: ^5 - lmdb: ^3 - peerDependenciesMeta: - ioredis: - optional: true - lmdb: - optional: true - '@effect/language-service@0.75.1': resolution: {integrity: sha512-g9xD2tAQgRFpYC2YgpZq02VeSL5fBbFJ0B/g1o+14NuNmwtaYJc7SjiLWAA9eyhJHosNrn6h1Ye+Kx6j5mN0AA==} hasBin: true - '@effect/platform-node-shared@0.57.1': - resolution: {integrity: sha512-oX/bApMdoKsyrDiNdJxo7U9Rz1RXsjRv+ecfAPp1qGlSdGIo32wVRvJ2XCHqYj0sqaYJS0pU0/GCulRfVGuJag==} - peerDependencies: - '@effect/cluster': ^0.56.1 - '@effect/platform': ^0.94.2 - '@effect/rpc': ^0.73.0 - '@effect/sql': ^0.49.0 - effect: ^3.19.15 - - '@effect/platform-node@0.104.1': - resolution: {integrity: sha512-jT1a/z98niK6fnEU8pWHPPCdJMVDRCIdB65lolcOjse5rsTwVbczMjvKkhVQpF63mNWoOnol7OTRNkw5L54llg==} - peerDependencies: - '@effect/cluster': ^0.56.1 - '@effect/platform': ^0.94.2 - '@effect/rpc': ^0.73.0 - '@effect/sql': ^0.49.0 - effect: ^3.19.15 - - '@effect/platform@0.94.5': - resolution: {integrity: sha512-z05APUiDDPbodhTkH/RJqOLoCU11bU2IZLfcwLFrld03+ob1VeqRnELQlmueLIYm6NZifHAtjl32V+GRt34y4A==} - peerDependencies: - effect: ^3.19.17 - - '@effect/printer-ansi@0.47.0': - resolution: {integrity: sha512-tDEQ9XJpXDNYoWMQJHFRMxKGmEOu6z32x3Kb8YLOV5nkauEKnKmWNs7NBp8iio/pqoJbaSwqDwUg9jXVquxfWQ==} - peerDependencies: - '@effect/typeclass': ^0.38.0 - effect: ^3.19.0 - - '@effect/printer@0.47.0': - resolution: {integrity: sha512-VgR8e+YWWhMEAh9qFOjwiZ3OXluAbcVLIOtvp2S5di1nSrPOZxj78g8LE77JSvyfp5y5bS2gmFW+G7xD5uU+2Q==} - peerDependencies: - '@effect/typeclass': ^0.38.0 - effect: ^3.19.0 - - '@effect/rpc@0.73.2': - resolution: {integrity: sha512-td7LHDgBOYKg+VgGWEelD8rSAmvjXz7am17vfxZROX5qIYuvH7drL/z4p5xQFadhHZ7DYdlFpqdO9ggc77OCIw==} - peerDependencies: - '@effect/platform': ^0.94.5 - effect: ^3.19.18 - - '@effect/schema@0.75.5': - resolution: {integrity: sha512-TQInulTVCuF+9EIbJpyLP6dvxbQJMphrnRqgexm/Ze39rSjfhJuufF7XvU3SxTgg3HnL7B/kpORTJbHhlE6thw==} - deprecated: this package has been merged into the main effect package - peerDependencies: - effect: ^3.9.2 - - '@effect/sql@0.49.0': - resolution: {integrity: sha512-9UEKR+z+MrI/qMAmSvb/RiD9KlgIazjZUCDSpwNgm0lEK9/Q6ExEyfziiYFVCPiptp52cBw8uBHRic8hHnwqXA==} - peerDependencies: - '@effect/experimental': ^0.58.0 - '@effect/platform': ^0.94.0 - effect: ^3.19.13 - - '@effect/typeclass@0.38.0': - resolution: {integrity: sha512-lMUcJTRtG8KXhXoczapZDxbLK5os7M6rn0zkvOgncJW++A0UyelZfMVMKdT5R+fgpZcsAU/1diaqw3uqLJwGxA==} - peerDependencies: - effect: ^3.19.0 - - '@effect/vitest@0.27.0': - resolution: {integrity: sha512-8bM7n9xlMUYw9GqPIVgXFwFm2jf27m/R7psI64PGpwU5+26iwyxp9eAXEsfT5S6lqztYfpQQ1Ubp5o6HfNYzJQ==} - peerDependencies: - effect: ^3.19.0 - vitest: ^3.2.0 - - '@effect/workflow@0.16.0': - resolution: {integrity: sha512-MiAdlxx3TixkgHdbw+Yf1Z3tHAAE0rOQga12kIydJqj05Fnod+W/I+kQGRMY/XWRg+QUsVxhmh1qTr7Ype6lrw==} - peerDependencies: - '@effect/experimental': ^0.58.0 - '@effect/platform': ^0.94.0 - '@effect/rpc': ^0.73.0 - effect: ^3.19.13 - '@emnapi/core@1.7.1': resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} @@ -778,36 +630,6 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': - resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} - cpu: [arm64] - os: [darwin] - - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': - resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} - cpu: [x64] - os: [darwin] - - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': - resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} - cpu: [arm64] - os: [linux] - - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': - resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} - cpu: [arm] - os: [linux] - - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': - resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} - cpu: [x64] - os: [linux] - - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': - resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} - cpu: [x64] - os: [win32] - '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -823,94 +645,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@parcel/watcher-android-arm64@2.5.1': - resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [android] - - '@parcel/watcher-darwin-arm64@2.5.1': - resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [darwin] - - '@parcel/watcher-darwin-x64@2.5.1': - resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [darwin] - - '@parcel/watcher-freebsd-x64@2.5.1': - resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [freebsd] - - '@parcel/watcher-linux-arm-glibc@2.5.1': - resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@parcel/watcher-linux-arm-musl@2.5.1': - resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - libc: [musl] - - '@parcel/watcher-linux-arm64-glibc@2.5.1': - resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@parcel/watcher-linux-arm64-musl@2.5.1': - resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@parcel/watcher-linux-x64-glibc@2.5.1': - resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@parcel/watcher-linux-x64-musl@2.5.1': - resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@parcel/watcher-win32-arm64@2.5.1': - resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [win32] - - '@parcel/watcher-win32-ia32@2.5.1': - resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} - engines: {node: '>= 10.0.0'} - cpu: [ia32] - os: [win32] - - '@parcel/watcher-win32-x64@2.5.1': - resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [win32] - - '@parcel/watcher@2.5.1': - resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} - engines: {node: '>= 10.0.0'} - '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -919,12 +653,6 @@ packages: resolution: {integrity: sha512-vWWVbYYBBN/kweokmURicokyg7crzcDZo9/naziv8B8RSWrLWFpq5Xl0ro6QCQKgRmb6O78Qy9uQT+Fp79RxsA==} engines: {node: '>=16.14'} - '@prover-coder-ai/eslint-plugin-suggest-members@0.0.25': - resolution: {integrity: sha512-J0oZtIz6IYeXWBgNLXaX2HyzSOcqTsjE+vzs/MQr7SKASvBYsyA7F34dQsh/8GM/kWBuSltkUsfv2RIcM6+t5Q==} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - '@rollup/rollup-android-arm-eabi@4.53.3': resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} cpu: [arm] @@ -1060,9 +788,6 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - '@ts-morph/common@0.28.1': - resolution: {integrity: sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==} - '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -1638,9 +1363,6 @@ packages: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} - code-block-writer@13.0.3: - resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1734,15 +1456,6 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} - detect-libc@1.0.3: - resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} - engines: {node: '>=0.10'} - hasBin: true - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2079,9 +1792,6 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - find-my-way-ts@0.1.6: - resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==} - find-up-simple@1.0.1: resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} engines: {node: '>=18'} @@ -2288,10 +1998,6 @@ packages: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} - ini@4.1.3: - resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -2565,9 +2271,6 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kubernetes-types@1.30.0: - resolution: {integrity: sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==} - levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -2639,11 +2342,6 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true - mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -2681,16 +2379,6 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msgpackr-extract@3.0.3: - resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} - hasBin: true - - msgpackr@1.11.5: - resolution: {integrity: sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==} - - multipasta@0.2.7: - resolution: {integrity: sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==} - nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2707,9 +2395,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-addon-api@7.1.1: - resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -2719,10 +2404,6 @@ packages: encoding: optional: true - node-gyp-build-optional-packages@5.2.2: - resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} - hasBin: true - node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -2846,9 +2527,6 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - path-browserify@1.0.1: - resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -3271,9 +2949,6 @@ packages: token-stream@1.0.0: resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==} - toml@3.0.0: - resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} - tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -3283,9 +2958,6 @@ packages: peerDependencies: typescript: '>=4.8.4' - ts-morph@27.0.2: - resolution: {integrity: sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==} - ts-pattern@5.9.0: resolution: {integrity: sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==} @@ -3384,10 +3056,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -3534,18 +3202,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -3873,120 +3529,14 @@ snapshots: '@dprint/typescript@0.91.8': {} - '@effect/cli@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(@effect/printer-ansi@0.47.0(@effect/typeclass@0.38.0(effect@3.19.18))(effect@3.19.18))(@effect/printer@0.47.0(@effect/typeclass@0.38.0(effect@3.19.18))(effect@3.19.18))(effect@3.19.18)': - dependencies: - '@effect/platform': 0.94.5(effect@3.19.18) - '@effect/printer': 0.47.0(@effect/typeclass@0.38.0(effect@3.19.18))(effect@3.19.18) - '@effect/printer-ansi': 0.47.0(@effect/typeclass@0.38.0(effect@3.19.18))(effect@3.19.18) - effect: 3.19.18 - ini: 4.1.3 - toml: 3.0.0 - yaml: 2.8.2 - - '@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(effect@3.19.18)': - dependencies: - '@effect/platform': 0.94.5(effect@3.19.18) - '@effect/rpc': 0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18) - '@effect/sql': 0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18) - '@effect/workflow': 0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18) - effect: 3.19.18 - kubernetes-types: 1.30.0 - '@effect/eslint-plugin@0.3.2': dependencies: '@dprint/formatter': 0.4.1 '@dprint/typescript': 0.91.8 prettier-linter-helpers: 1.0.0 - '@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18)': - dependencies: - '@effect/platform': 0.94.5(effect@3.19.18) - effect: 3.19.18 - uuid: 11.1.0 - '@effect/language-service@0.75.1': {} - '@effect/platform-node-shared@0.57.1(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18)': - dependencies: - '@effect/cluster': 0.56.4(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(effect@3.19.18) - '@effect/platform': 0.94.5(effect@3.19.18) - '@effect/rpc': 0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18) - '@effect/sql': 0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18) - '@parcel/watcher': 2.5.1 - effect: 3.19.18 - multipasta: 0.2.7 - ws: 8.18.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - '@effect/platform-node@0.104.1(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18)': - dependencies: - '@effect/cluster': 0.56.4(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(effect@3.19.18) - '@effect/platform': 0.94.5(effect@3.19.18) - '@effect/platform-node-shared': 0.57.1(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18) - '@effect/rpc': 0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18) - '@effect/sql': 0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18) - effect: 3.19.18 - mime: 3.0.0 - undici: 7.16.0 - ws: 8.18.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - '@effect/platform@0.94.5(effect@3.19.18)': - dependencies: - effect: 3.19.18 - find-my-way-ts: 0.1.6 - msgpackr: 1.11.5 - multipasta: 0.2.7 - - '@effect/printer-ansi@0.47.0(@effect/typeclass@0.38.0(effect@3.19.18))(effect@3.19.18)': - dependencies: - '@effect/printer': 0.47.0(@effect/typeclass@0.38.0(effect@3.19.18))(effect@3.19.18) - '@effect/typeclass': 0.38.0(effect@3.19.18) - effect: 3.19.18 - - '@effect/printer@0.47.0(@effect/typeclass@0.38.0(effect@3.19.18))(effect@3.19.18)': - dependencies: - '@effect/typeclass': 0.38.0(effect@3.19.18) - effect: 3.19.18 - - '@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18)': - dependencies: - '@effect/platform': 0.94.5(effect@3.19.18) - effect: 3.19.18 - msgpackr: 1.11.5 - - '@effect/schema@0.75.5(effect@3.19.18)': - dependencies: - effect: 3.19.18 - fast-check: 3.23.2 - - '@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18)': - dependencies: - '@effect/experimental': 0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18) - '@effect/platform': 0.94.5(effect@3.19.18) - effect: 3.19.18 - uuid: 11.1.0 - - '@effect/typeclass@0.38.0(effect@3.19.18)': - dependencies: - effect: 3.19.18 - - '@effect/vitest@0.27.0(effect@3.19.18)(vitest@4.0.18(@types/node@24.10.13)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': - dependencies: - effect: 3.19.18 - vitest: 4.0.18(@types/node@24.10.13)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) - - '@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18)': - dependencies: - '@effect/experimental': 0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18) - '@effect/platform': 0.94.5(effect@3.19.18) - '@effect/rpc': 0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18) - effect: 3.19.18 - '@emnapi/core@1.7.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -4261,24 +3811,6 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': - optional: true - '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.7.1 @@ -4298,88 +3830,11 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@parcel/watcher-android-arm64@2.5.1': - optional: true - - '@parcel/watcher-darwin-arm64@2.5.1': - optional: true - - '@parcel/watcher-darwin-x64@2.5.1': - optional: true - - '@parcel/watcher-freebsd-x64@2.5.1': - optional: true - - '@parcel/watcher-linux-arm-glibc@2.5.1': - optional: true - - '@parcel/watcher-linux-arm-musl@2.5.1': - optional: true - - '@parcel/watcher-linux-arm64-glibc@2.5.1': - optional: true - - '@parcel/watcher-linux-arm64-musl@2.5.1': - optional: true - - '@parcel/watcher-linux-x64-glibc@2.5.1': - optional: true - - '@parcel/watcher-linux-x64-musl@2.5.1': - optional: true - - '@parcel/watcher-win32-arm64@2.5.1': - optional: true - - '@parcel/watcher-win32-ia32@2.5.1': - optional: true - - '@parcel/watcher-win32-x64@2.5.1': - optional: true - - '@parcel/watcher@2.5.1': - dependencies: - detect-libc: 1.0.3 - is-glob: 4.0.3 - micromatch: 4.0.8 - node-addon-api: 7.1.1 - optionalDependencies: - '@parcel/watcher-android-arm64': 2.5.1 - '@parcel/watcher-darwin-arm64': 2.5.1 - '@parcel/watcher-darwin-x64': 2.5.1 - '@parcel/watcher-freebsd-x64': 2.5.1 - '@parcel/watcher-linux-arm-glibc': 2.5.1 - '@parcel/watcher-linux-arm-musl': 2.5.1 - '@parcel/watcher-linux-arm64-glibc': 2.5.1 - '@parcel/watcher-linux-arm64-musl': 2.5.1 - '@parcel/watcher-linux-x64-glibc': 2.5.1 - '@parcel/watcher-linux-x64-musl': 2.5.1 - '@parcel/watcher-win32-arm64': 2.5.1 - '@parcel/watcher-win32-ia32': 2.5.1 - '@parcel/watcher-win32-x64': 2.5.1 - '@pkgjs/parseargs@0.11.0': optional: true '@pnpm/deps.graph-sequencer@1.0.0': {} - '@prover-coder-ai/eslint-plugin-suggest-members@0.0.25(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(eslint@10.0.1(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@effect/platform': 0.94.5(effect@3.19.18) - '@effect/platform-node': 0.104.1(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(@effect/platform@0.94.5(effect@3.19.18))(effect@3.19.18))(effect@3.19.18) - '@effect/schema': 0.75.5(effect@3.19.18) - '@typescript-eslint/utils': 8.55.0(eslint@10.0.1(jiti@2.6.1))(typescript@5.9.3) - effect: 3.19.18 - eslint: 10.0.1(jiti@2.6.1) - typescript: 5.9.3 - transitivePeerDependencies: - - '@effect/cluster' - - '@effect/rpc' - - '@effect/sql' - - bufferutil - - supports-color - - utf-8-validate - '@rollup/rollup-android-arm-eabi@4.53.3': optional: true @@ -4462,12 +3917,6 @@ snapshots: loop-controls: 1.1.0 ts-pattern: 5.9.0 - '@ts-morph/common@0.28.1': - dependencies: - minimatch: 10.1.2 - path-browserify: 1.0.1 - tinyglobby: 0.2.15 - '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -5083,8 +4532,6 @@ snapshots: optionalDependencies: '@colors/colors': 1.5.0 - code-block-writer@13.0.3: {} - color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -5170,11 +4617,6 @@ snapshots: detect-indent@6.1.0: {} - detect-libc@1.0.3: {} - - detect-libc@2.1.2: - optional: true - diff-sequences@29.6.3: {} dir-glob@3.0.1: @@ -5674,8 +5116,6 @@ snapshots: dependencies: to-regex-range: 5.0.1 - find-my-way-ts@0.1.6: {} - find-up-simple@1.0.1: {} find-up@4.1.0: @@ -5881,8 +5321,6 @@ snapshots: indent-string@5.0.0: {} - ini@4.1.3: {} - internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -6185,8 +5623,6 @@ snapshots: dependencies: json-buffer: 3.0.1 - kubernetes-types@1.30.0: {} - levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -6264,8 +5700,6 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mime@3.0.0: {} - mimic-fn@2.1.0: {} minimatch@10.1.2: @@ -6296,24 +5730,6 @@ snapshots: ms@2.1.3: {} - msgpackr-extract@3.0.3: - dependencies: - node-gyp-build-optional-packages: 5.2.2 - optionalDependencies: - '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 - optional: true - - msgpackr@1.11.5: - optionalDependencies: - msgpackr-extract: 3.0.3 - - multipasta@0.2.7: {} - nanoid@3.3.11: {} napi-postinstall@0.3.4: {} @@ -6322,17 +5738,10 @@ snapshots: natural-compare@1.4.0: {} - node-addon-api@7.1.1: {} - node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 - node-gyp-build-optional-packages@5.2.2: - dependencies: - detect-libc: 2.1.2 - optional: true - node-releases@2.0.27: {} node-sarif-builder@3.4.0: @@ -6482,8 +5891,6 @@ snapshots: dependencies: entities: 6.0.1 - path-browserify@1.0.1: {} - path-exists@4.0.0: {} path-key@3.1.1: {} @@ -6953,19 +6360,12 @@ snapshots: token-stream@1.0.0: {} - toml@3.0.0: {} - tr46@0.0.3: {} ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 - ts-morph@27.0.2: - dependencies: - '@ts-morph/common': 0.28.1 - code-block-writer: 13.0.3 - ts-pattern@5.9.0: {} tsconfck@3.1.6(typescript@5.9.3): @@ -7096,8 +6496,6 @@ snapshots: dependencies: punycode: 2.3.1 - uuid@11.1.0: {} - validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 @@ -7253,11 +6651,10 @@ snapshots: wrappy@1.0.2: {} - ws@8.18.3: {} - yallist@3.1.1: {} - yaml@2.8.2: {} + yaml@2.8.2: + optional: true yocto-queue@0.1.0: {}