Skip to content

Commit a32ea3e

Browse files
Sander Toonenclaude
andcommitted
Split datetime docs by audience; promote main README to repo root
Documentation: * docs/datetime.md is now the expression-writer page (function reference + recipes + a brief note that the developer needs to install @pro-fa/expreszo-datetime). Moved from "For Developers" into "For Expression Writers" in the mkdocs nav. * docs/datetime-integration.md (new) is the developer page — install instructions, parser.use(plugin) wiring, the polymorphic input contract, the spread-into-defineParser fallback, and bundle hygiene notes. Lives under "For Developers" in the nav. * docs/index.md cross-links updated for the split. * mkdocs build --strict passes. README: * packages/expreszo/README.md content moved to repo root README.md (replacing the previous workspace-overview README). All in-README links now use absolute github.com URLs so the same file works rendered at the repo root, inside the npm tarball, and on npmjs.com — npm's renderer can't resolve relative monorepo paths. * Added a "Working in this repository" section at the bottom for contributors with the workspace commands. * packages/expreszo/README.md is now a small stub pointing back at the root README. * publish.yml gains a "Sync canonical README into expreszo package" step (only fires for the expreszo matrix entry) that copies the root README into packages/expreszo/ right before npm publish, so the published tarball always carries the full README content. * CLAUDE.md gains a "README convention" section documenting that the root file is canonical and the per-package stub must not be edited directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 047552e commit a32ea3e

8 files changed

Lines changed: 318 additions & 267 deletions

File tree

.github/workflows/publish.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ jobs:
4646
- name: Test (all workspaces)
4747
run: yarn workspaces run test
4848

49+
- name: Sync canonical README into expreszo package
50+
# The repo-root README is the source of truth for @pro-fa/expreszo;
51+
# the package directory ships a stub during dev, replaced here so the
52+
# published npm tarball gets the full content.
53+
if: matrix.package == 'expreszo'
54+
run: cp README.md packages/expreszo/README.md
55+
4956
- name: Show package identity
5057
working-directory: packages/${{ matrix.package }}
5158
run: |

CLAUDE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,11 @@ Each package's `vite.config.ts` is invoked via `BUILD_TARGET` env var:
125125
## Code style
126126

127127
ESLint at the workspace root (`eslint.config.js`) enforces: semicolons, single quotes, 2-space indentation. TypeScript strict mode is on. `@typescript-eslint/no-explicit-any` is relaxed.
128+
129+
## README convention
130+
131+
The repo-root `README.md` is the canonical README for `@pro-fa/expreszo` — it's what GitHub shows on the repo home and what the npm registry shows on the package page. Edit it there.
132+
133+
`packages/expreszo/README.md` is a stub that points back at the root README. The publish workflow (`publish.yml`) copies the root README into `packages/expreszo/` right before `npm publish`, so the npm tarball always carries the full content. **Do not edit the stub** — make changes at the root.
134+
135+
The other two packages (`@pro-fa/expreszo-datetime`, `@pro-fa/expreszo-mcp-server`) have their own self-contained READMEs in their package directories.

README.md

Lines changed: 174 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,190 @@
1-
# ExpresZo (monorepo)
1+
<picture>
2+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Pro-Fa/expreszo-typescript/main/packages/expreszo/docs/logo_dark.png">
3+
<img src="https://raw.githubusercontent.com/Pro-Fa/expreszo-typescript/main/packages/expreszo/docs/logo.png" alt="ExpresZo" width="420">
4+
</picture>
25

3-
This repository hosts the ExpresZo expression-evaluator core and its companion packages.
6+
# ExpresZo Typescript
7+
[![npm](https://img.shields.io/npm/v/@pro-fa/expreszo.svg?maxAge=3600)](https://www.npmjs.com/package/@pro-fa/expreszo)
8+
[![CI](https://github.com/Pro-Fa/expreszo-typescript/actions/workflows/ci.yml/badge.svg)](https://github.com/Pro-Fa/expreszo-typescript/actions/workflows/ci.yml)
9+
[![codecov](https://codecov.io/gh/Pro-Fa/expreszo-typescript/branch/main/graph/badge.svg)](https://codecov.io/gh/Pro-Fa/expreszo-typescript)
410

5-
## Packages
11+
**A fast, safe, and extensible expression evaluator for JavaScript and TypeScript.**
612

7-
| Package | Description |
8-
|---------|-------------|
9-
| [`@pro-fa/expreszo`](packages/expreszo) | Core safe, extensible expression evaluator. Pratt parser + immutable AST + descriptor-based extensibility. |
10-
| [`@pro-fa/expreszo-datetime`](packages/expreszo-datetime) | Optional date/time functions backed by [Luxon](https://moment.github.io/luxon/). Adds `now`, `parseISO`, `addDuration`, `format`, … via a single `dateTimePlugin`. |
11-
| [`@pro-fa/expreszo-mcp-server`](packages/expreszo-mcp-server) | MCP server exposing the parser to AI assistants. Ships its own `expreszo-mcp` CLI. |
13+
ExpresZo parses and evaluates expressions at runtime — a configurable alternative to `eval()` that won't execute arbitrary code. Use it to power user-facing formula editors, rule engines, template systems, or any place you need to evaluate dynamic expressions safely.
1214

13-
## Quick start
15+
```js
16+
import { Parser } from '@pro-fa/expreszo';
1417

15-
```bash
16-
yarn install
17-
yarn workspaces run build
18-
yarn workspaces run test
18+
const parser = new Parser();
19+
parser.evaluate('price * (1 - discount)', { price: 100, discount: 0.2 }); // 80
1920
```
2021

21-
## Adding a date/time plugin to a parser
22+
[Read full documentation](https://pro-fa.github.io/expreszo-typescript/)
23+
24+
## Companion packages
25+
26+
This package is the core. Two optional companions extend it without bloating the core install — pick them up only when you need them:
27+
28+
- **[`@pro-fa/expreszo-datetime`](https://www.npmjs.com/package/@pro-fa/expreszo-datetime)**~70 [Luxon](https://moment.github.io/luxon/)-backed date/time functions (`now`, `parseISO`, `addDuration`, `format`, `isWeekend`, `daysUntil`, `toRelative`, `dateRange`, …). Polymorphic inputs accept Luxon `DateTime`, JS `Date`, ISO strings, and millisecond timestamps. The core never imports Luxon.
29+
- **[`@pro-fa/expreszo-mcp-server`](https://www.npmjs.com/package/@pro-fa/expreszo-mcp-server)** — MCP server that exposes the language service to AI assistants (Claude Desktop, Claude Code, Cursor, …). Ships an `expreszo-mcp` CLI.
30+
31+
Companions register with the new `parser.use(plugin)` API:
2232

2333
```ts
2434
import { defineParser, fullParser } from '@pro-fa/expreszo';
2535
import { dateTimePlugin } from '@pro-fa/expreszo-datetime';
2636

27-
const parser = defineParser({ ...fullParser })
28-
.use(dateTimePlugin);
37+
const parser = defineParser({ ...fullParser }).use(dateTimePlugin);
2938

3039
parser.parse("format(addDuration(now(), 7, 'days'), 'yyyy-MM-dd')").evaluate();
3140
```
3241

33-
`@pro-fa/expreszo` itself does not depend on Luxon — it is pulled in only when you install `@pro-fa/expreszo-datetime`.
42+
## Why ExpresZo?
43+
44+
### Fast
45+
46+
ExpresZo uses a **Pratt parser** — a top-down operator-precedence parsing algorithm that processes tokens in a single pass with no backtracking. Compared to the recursive-descent parser in the original expr-eval, this means:
47+
48+
- **Significantly faster parsing** — simple expressions parse in microseconds, complex ones at 40,000+ ops/sec
49+
- **Predictable performance** — parsing time scales linearly with expression length, not exponentially with nesting depth
50+
- **Better error messages** — the parser knows exactly what it expected at each position, producing precise diagnostics instead of generic "parse error" messages
51+
- **Depth-limited** — a 256-level recursion cap prevents stack overflow from malicious or runaway input
52+
53+
Parsed expressions compile to an **immutable AST** that can be evaluated repeatedly against different variable sets with near-zero overhead.
54+
55+
### Safe
56+
57+
ExpresZo is designed to be safe by default:
58+
59+
- **No code execution** — expressions can only call explicitly registered functions, never arbitrary JavaScript
60+
- **Prototype pollution protection** — access to `__proto__`, `prototype`, and `constructor` is blocked
61+
- **Recursion depth limit** — deeply nested expressions are rejected at parse time
62+
- **No `eval()` or `new Function()`** — the entire evaluation runs on a stack-based AST walker
63+
64+
### Extensible
65+
66+
Build exactly the parser you need:
67+
68+
```typescript
69+
import { defineParser, coreParser, withMath, withString } from '@pro-fa/expreszo';
70+
71+
// Tree-shakeable: only include what you use
72+
const parser = defineParser({
73+
operators: [...coreParser.operators, ...withMath.operators, ...withString.operators],
74+
functions: [...coreParser.functions, ...withMath.functions, ...withString.functions],
75+
});
76+
```
77+
78+
Or use the full kitchen-sink parser with zero configuration:
79+
80+
```typescript
81+
const parser = new Parser(); // all built-in operators and functions included
82+
```
83+
84+
## Installation
85+
86+
```bash
87+
npm install @pro-fa/expreszo
88+
```
89+
90+
## Quick Start
91+
92+
```typescript
93+
import { Parser } from '@pro-fa/expreszo';
94+
95+
const parser = new Parser();
96+
97+
// Parse once, evaluate many times
98+
const expr = parser.parse('2 * x + 1');
99+
expr.evaluate({ x: 3 }); // 7
100+
expr.evaluate({ x: 10 }); // 21
101+
102+
// Or pass a resolver directly as the first argument
103+
expr.evaluate((name) => name === 'x' ? { value: 3 } : undefined); // 7
104+
105+
// Rich expression language
106+
parser.evaluate('user.name ?? "Anonymous"', { user: {} }); // "Anonymous"
107+
parser.evaluate('CASE WHEN score >= 90 THEN "A" WHEN score >= 80 THEN "B" ELSE "C" END', { score: 85 }); // "B"
108+
```
109+
110+
## Key Features
111+
112+
| Category | Features |
113+
|----------|----------|
114+
| **Operators** | Arithmetic, comparison, logical, coalesce (`??`), ternary, assignment, member access |
115+
| **Data types** | Numbers, strings, booleans, arrays, objects, `null`, `undefined` |
116+
| **Functions** | 60+ built-in: math, string, array, object, type-checking, utility |
117+
| **Custom functions** | Register your own JavaScript functions callable from expressions |
118+
| **Arrow functions** | `x => x * 2`, `(a, b) => a + b` |
119+
| **SQL CASE** | `CASE WHEN ... THEN ... ELSE ... END` multi-way conditionals |
120+
| **Object construction** | `{ name: "Ada", score: x * 10 }` |
121+
| **Async support** | Custom functions can return Promises; `evaluate()` auto-awaits |
122+
| **Language service** | Completions, hover docs, diagnostics, syntax highlighting for IDE integration |
123+
| **TypeScript** | Full type definitions, strict mode, no `any` leaks |
124+
| **Tree-shakeable** | Subpath imports (`@pro-fa/expreszo/math`, `@pro-fa/expreszo/string`, ...) for minimal bundles |
125+
126+
## Playground
127+
128+
Try it live at the [Playground](https://pro-fa.github.io/expreszo-typescript/) — an interactive environment with code completions, syntax highlighting, and real-time evaluation.
129+
130+
## Documentation
131+
132+
### For Expression Writers
133+
134+
| Document | Description |
135+
|:---------|:------------|
136+
| [Quick Reference](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/quick-reference.md) | Cheat sheet of operators, functions, and syntax |
137+
| [Expression Syntax](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/syntax.md) | Complete syntax reference with examples |
138+
| [Date / Time functions](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/datetime.md) | ~70 date/time functions (provided by the optional `@pro-fa/expreszo-datetime` package) |
139+
140+
### For Developers
141+
142+
| Document | Description |
143+
|:---------|:------------|
144+
| [Parser](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/parser.md) | Parser configuration, methods, customization, and the `parser.use(plugin)` API |
145+
| [Expression](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/expression.md) | Expression object methods: evaluate, simplify, variables |
146+
| [Advanced Features](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/advanced-features.md) | Promises, custom resolution, type conversion, operator customization |
147+
| [Date / Time integration](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/datetime-integration.md) | Wiring `@pro-fa/expreszo-datetime` into a parser |
148+
| [Language Service](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/language-service.md) | IDE integration: completions, hover info, diagnostics, Monaco Editor |
149+
| [MCP Server](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/mcp-server.md) | Model Context Protocol server exposing the language service to AI assistants |
150+
| [Migration Guide](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/migration.md) | Migrating from expr-eval, legacy mode, version history |
151+
152+
### For Contributors
153+
154+
| Document | Description |
155+
|:---------|:------------|
156+
| [Contributing](https://github.com/Pro-Fa/expreszo-typescript/blob/main/CONTRIBUTING.md) | Development setup, code style, and PR guidelines |
157+
| [Performance Testing](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/performance.md) | Benchmarks, profiling, and optimization guidance |
158+
| [Breaking Changes](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/BREAKING_CHANGES.md) | Version-by-version breaking change documentation |
159+
160+
## Coming from expr-eval?
161+
162+
ExpresZo is a direct successor to [expr-eval](https://github.com/silentmatt/expr-eval). Existing expressions work out of the box. A `{ legacy: true }` option preserves older operator semantics while you migrate incrementally. See the [Migration Guide](https://github.com/Pro-Fa/expreszo-typescript/blob/main/packages/expreszo/docs/migration.md) for details.
163+
164+
## Origins
165+
166+
Originally based on [expr-eval 2.0.2](http://silentmatt.com/javascript-expression-evaluator/), completely rewritten with a Pratt parser, immutable AST, modular architecture, TypeScript, and comprehensive testing using Vitest.
167+
168+
## Working in this repository
169+
170+
The repo is a **yarn workspace monorepo** with three published packages under `packages/`:
171+
172+
| Package | Path |
173+
|---------|------|
174+
| `@pro-fa/expreszo` | `packages/expreszo/` |
175+
| `@pro-fa/expreszo-datetime` | `packages/expreszo-datetime/` |
176+
| `@pro-fa/expreszo-mcp-server` | `packages/expreszo-mcp-server/` |
177+
178+
```bash
179+
yarn install --frozen-lockfile # install all workspaces
180+
yarn workspaces run lint # eslint all packages
181+
yarn workspaces run type-check # tsc/tsgo on all packages
182+
yarn workspaces run build # produce dist/ for each package
183+
yarn workspaces run test # build + vitest in each package
184+
```
185+
186+
This file (`README.md`) is the canonical README. The publish workflow copies it into `packages/expreszo/` before `npm publish`, so the npm package always ships the same content.
187+
188+
## License
189+
190+
See [LICENSE.txt](https://github.com/Pro-Fa/expreszo-typescript/blob/main/LICENSE.txt) for license information.

0 commit comments

Comments
 (0)