@carlos-tzin/tzin 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.4 — Node 20 compatibility fix
4
+
5
+ No API changes. CI was failing on the Node 20 floor because the WS test and the
6
+ Workers probe relied on the global `WebSocket`, which only exists on Node ≥22.
7
+
8
+ ### Testing & coverage
9
+
10
+ - `native websockets` test and the Workers probe use the `ws` package client
11
+ instead of the global `WebSocket`, keeping the Node 20 floor green
12
+
13
+ No API changes. Focused on proving the stable claim in CI and on the
14
+ type surface.
15
+
16
+ ### Testing & coverage
17
+
18
+ - Unit suites for `cache.ts` and `rate-limit.ts` (30 tests) — previously the
19
+ only modules without direct coverage
20
+ - Coverage gates in CI: `npm run test:coverage` requires ≥70% lines, functions,
21
+ statements and branches across `src/` (baseline 73.6%)
22
+ - `vitest.config.ts` with the v8 provider; `coverage/` git-ignored
23
+
24
+ ### CI matrix
25
+
26
+ - Node `20 / 22 / 24` — previously only 22 and 24; Node 20 is the documented
27
+ floor and is now exercised
28
+ - Cloudflare Workers probe (`miniflare`) runs on every push, not just releases
29
+ - Benchmark job (`lookup`, `pipeline`, `http`) runs in CI with minimal rounds
30
+ to catch runtime regressions early
31
+
32
+ ### Type-surface cleanup
33
+
34
+ - Public `RouteImpl<any>` occurrences replaced by the new erasable
35
+ `AnyRoute` type — `createApp`, MCP tools, OpenAPI, llms.txt, channels and
36
+ route-table printing now take `AnyRoute[]` while `impl()` keeps full
37
+ per-endpoint typing
38
+ - Browser client callbacks (`on`) and internal `any` returns typed as
39
+ `unknown`; `client()` proxy input uses a typed `ClientInput`
40
+ - New exports: `AnyRoute`, `RouteResult`
41
+
3
42
  ## 1.0.1 — Repo moved to Tzinny-dev
4
43
 
5
44
  Metadata-only release after the source repository moved from
package/README.md CHANGED
@@ -9,8 +9,9 @@
9
9
  [![npm](https://img.shields.io/npm/v/@carlos-tzin/tzin)](https://www.npmjs.com/package/@carlos-tzin/tzin)
10
10
  [![Donate](https://img.shields.io/badge/Donate-PayPal-00457C?logo=paypal&logoColor=white)](https://paypal.me/carlostzin)
11
11
 
12
- **Status: experimental, pre-1.0.** The core works end-to-end and the scaling thesis is
13
- measured (see [Benchmarks](#benchmarks)), but this is not yet production software.
12
+ **Status: stable (1.0.x).** The core works end-to-end (see [Benchmarks](#benchmarks));
13
+ 1.0.0 shipped the stable API, and 1.0.2 adds test coverage gates, a wider CI
14
+ matrix and a public type-clean surface.
14
15
 
15
16
  ```sh
16
17
  npm install @carlos-tzin/tzin
@@ -1,4 +1,4 @@
1
- import { type RouteImpl } from './contract.js';
1
+ import { type AnyRoute } from './contract.js';
2
2
  import type { Hub } from './hub.js';
3
3
  import type { Presence } from './presence.js';
4
4
  export interface ChannelOptions {
@@ -11,4 +11,4 @@ export interface ChannelOptions {
11
11
  * POST /channels/:topic/heartbeat presence join/refresh { member, meta? }
12
12
  * POST /channels/:topic/leave presence leave { member }
13
13
  */
14
- export declare function channelRoutes(hub: Hub, options?: ChannelOptions): RouteImpl<any>[];
14
+ export declare function channelRoutes(hub: Hub, options?: ChannelOptions): AnyRoute[];
@@ -33,7 +33,7 @@ export type Unsubscribe = () => void;
33
33
  export interface Channel {
34
34
  readonly topic: string;
35
35
  /** Listen to a named event; returns an unsubscribe function. */
36
- on(event: string, cb: (data: any) => void): Unsubscribe;
36
+ on(event: string, cb: (data: unknown) => void): Unsubscribe;
37
37
  /** Broadcast to every subscriber of this channel. */
38
38
  push(event: string, data?: unknown): Promise<{
39
39
  delivered: number;
@@ -62,6 +62,23 @@ export interface RouteImpl<C extends AnyContract = AnyContract> {
62
62
  }
63
63
  /** Bind an implementation to a contract. The compiler checks inputs AND outputs. */
64
64
  export declare function impl<const C extends AnyContract>(c: C, handler: Handler<C>): RouteImpl<C>;
65
+ /** A handler's resolved reply with the per-endpoint generics erased. */
66
+ export type RouteResult = {
67
+ status: number;
68
+ body: unknown;
69
+ } | RawResult;
70
+ /**
71
+ * A route with its contract generics erased, for heterogeneous registries —
72
+ * `createApp`, OpenAPI/MCP/llms generators, docs tools. Each concrete
73
+ * `RouteImpl<C>` satisfies it (impl keeps inputs/outputs fully typed); the
74
+ * erased handler is only invoked by dispatchers, which supply validated
75
+ * sections via a cast (the `never` input makes accidental direct calls a
76
+ * compile error).
77
+ */
78
+ export interface AnyRoute {
79
+ contract: AnyContract;
80
+ handler: (input: never) => RouteResult | Promise<RouteResult>;
81
+ }
65
82
  /** Thrown inside handlers -> converted to an HTTP error response. */
66
83
  export declare class HttpError extends Error {
67
84
  readonly status: number;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { contract, impl, HttpError, type HttpMethod, type ContractDef, type AnyContract, type PathParamNames, type HandlerInput, type SectionsOf, type ResponseOf, type Handler, type RouteImpl, } from './contract.js';
1
+ export { contract, impl, HttpError, type HttpMethod, type ContractDef, type AnyContract, type PathParamNames, type HandlerInput, type SectionsOf, type ResponseOf, type Handler, type RouteImpl, type AnyRoute, type RouteResult, } from './contract.js';
2
2
  export { createApp, type App } from './server.js';
3
3
  export { client, type ClientOf, type ClientResult, type CallerFn } from './client.js';
4
4
  export { generateOpenApi } from './openapi.js';
package/dist/llms.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import type { RouteImpl } from './contract.js';
1
+ import type { AnyRoute } from './contract.js';
2
2
  export interface ApiMeta {
3
3
  title?: string;
4
4
  description?: string;
5
5
  version?: string;
6
6
  }
7
7
  /** llms.txt: the discoverable, human-readable index of the API. */
8
- export declare function renderLlmsTxt(routes: RouteImpl<any>[], meta?: ApiMeta): string;
8
+ export declare function renderLlmsTxt(routes: AnyRoute[], meta?: ApiMeta): string;
9
9
  /** llms-full.txt: same index plus each endpoint's declared JSON Schemas. */
10
- export declare function renderLlmsFullTxt(routes: RouteImpl<any>[], meta?: ApiMeta): string;
10
+ export declare function renderLlmsFullTxt(routes: AnyRoute[], meta?: ApiMeta): string;
package/dist/llms.js CHANGED
@@ -32,8 +32,9 @@ export function renderLlmsFullTxt(routes, meta = {}) {
32
32
  ...(c.description ? [`${c.description}`, ''] : []),
33
33
  ];
34
34
  for (const section of ['params', 'query', 'headers', 'cookies', 'body']) {
35
- if (section in c && c[section]) {
36
- lines.push(`\`${section}\` schema:`, '', '```json', JSON.stringify(c[section]), '```', '');
35
+ const schema = c[section];
36
+ if (schema) {
37
+ lines.push(`\`${section}\` schema:`, '', '```json', JSON.stringify(schema), '```', '');
37
38
  }
38
39
  }
39
40
  for (const [status, schema] of Object.entries(c.responses)) {
package/dist/mcp.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import type { App } from './server.js';
2
- import type { RouteImpl } from './contract.js';
2
+ import type { AnyRoute } from './contract.js';
3
3
  /**
4
4
  * One endpoint = one MCP tool.
5
5
  * The inputSchema is assembled from the contract's declared sections —
6
6
  * TypeBox schemas are already JSON Schema, so there is no conversion layer.
7
7
  */
8
- export declare function toTool(route: RouteImpl<any>): Record<string, unknown>;
9
- export declare function listTools(routes: RouteImpl<any>[]): Record<string, unknown>[];
8
+ export declare function toTool(route: AnyRoute): Record<string, unknown>;
9
+ export declare function listTools(routes: AnyRoute[]): Record<string, unknown>[];
10
10
  export interface RpcRequest {
11
11
  jsonrpc?: string;
12
12
  id?: string | number | null;
package/dist/openapi.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import type { RouteImpl } from './contract.js';
1
+ import type { AnyRoute } from './contract.js';
2
2
  /**
3
3
  * OpenAPI 3.1 document generated from contracts.
4
4
  * Zero conversion layer: TypeBox == JSON Schema == OpenAPI component vocabulary.
5
5
  */
6
- export declare function generateOpenApi(routes: RouteImpl<any>[], info?: {
6
+ export declare function generateOpenApi(routes: AnyRoute[], info?: {
7
7
  title: string;
8
8
  version: string;
9
9
  }): Record<string, unknown>;
package/dist/server.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { type RouteImpl } from './contract.js';
1
+ import { type AnyRoute } from './contract.js';
2
2
  import { type Middleware } from './middleware.js';
3
3
  import type { ProvidedEntry } from './provide.js';
4
4
  import { type ApiMeta } from './llms.js';
5
5
  export interface App {
6
- routes: RouteImpl<any>[];
6
+ routes: AnyRoute[];
7
7
  fetch(req: Request): Promise<Response>;
8
8
  /**
9
9
  * Same dispatch as fetch(), without constructing undici Responses for
@@ -42,4 +42,4 @@ export declare function fastResponseHeaders(res: Response): Record<string, strin
42
42
  * stay lazy; real Request objects carry a plain signal.
43
43
  */
44
44
  export declare function signalSource(req: Request): AbortSignal | (() => AbortSignal | undefined) | undefined;
45
- export declare function createApp(routes: RouteImpl<any>[], options?: AppOptions): App;
45
+ export declare function createApp(routes: AnyRoute[], options?: AppOptions): App;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carlos-tzin/tzin",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Contract-first TypeScript framework. Types that scale, realtime channels with presence, and an MCP server for every API.",
5
5
  "license": "MIT",
6
6
  "author": "The tzin authors",
@@ -85,6 +85,7 @@
85
85
  "build": "tsc -p tsconfig.build.json",
86
86
  "prepublishOnly": "npm run build && npm test",
87
87
  "test": "vitest run",
88
+ "test:coverage": "vitest run --coverage",
88
89
  "typecheck": "tsc --noEmit",
89
90
  "typecheck:ours": "tsc -p bench/tsconfig.ours.json --noEmit --extendedDiagnostics",
90
91
  "typecheck:hono": "tsc -p bench/tsconfig.hono.json --noEmit --extendedDiagnostics",
@@ -103,7 +104,9 @@
103
104
  "@hono/node-server": "^2.1.1",
104
105
  "@types/node": "^26.2.0",
105
106
  "@types/ws": "^8.18.1",
107
+ "@vitest/coverage-v8": "^2.1.9",
106
108
  "autocannon": "^8.0.0",
109
+ "esbuild": "^0.28.0",
107
110
  "express": "^5.2.1",
108
111
  "hono": "^4.0.0",
109
112
  "miniflare": "^5.20260820.0-alpha",