Skip to content

Commit 047552e

Browse files
Merge pull request #7 from Pro-Fa/feat/workspace-monorepo-and-datetime
Workspace monorepo + Plugin API + datetime/MCP packages
2 parents eb05e7b + 2f7c220 commit 047552e

268 files changed

Lines changed: 7305 additions & 9011 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ jobs:
1414
runs-on: ubuntu-latest
1515

1616
strategy:
17+
fail-fast: false
1718
matrix:
18-
node-version: [18, 20, 22]
19+
node-version: [20, 22]
1920

2021
steps:
2122
- name: Checkout code
@@ -25,29 +26,37 @@ jobs:
2526
uses: actions/setup-node@v4
2627
with:
2728
node-version: ${{ matrix.node-version }}
28-
cache: 'npm'
29+
cache: 'yarn'
2930

3031
- name: Install dependencies
31-
run: npm ci
32+
run: yarn install --frozen-lockfile
3233

33-
- name: Run lint
34-
run: npm run lint
34+
- name: Lint (all workspaces)
35+
run: yarn workspaces run lint
3536

36-
- name: Run type check
37-
run: npm run type-check
37+
- name: Build (all workspaces)
38+
# Must run before type-check: datetime and mcp-server resolve
39+
# `@pro-fa/expreszo` via its built dist/index.d.ts, which the alphabetical
40+
# workspace order produces first.
41+
run: yarn workspaces run build
3842

39-
- name: Run tests
40-
run: npm test
43+
- name: Type-check (all workspaces)
44+
run: yarn workspaces run type-check
4145

42-
- name: Run coverage
46+
- name: Test (all workspaces)
47+
run: yarn workspaces run test
48+
49+
- name: Coverage (core package)
4350
if: matrix.node-version == 20
44-
run: npm run coverage
51+
working-directory: packages/expreszo
52+
run: yarn coverage
4553

4654
- name: Upload coverage reports to Codecov
4755
if: matrix.node-version == 20
4856
uses: codecov/codecov-action@v5
4957
with:
5058
token: ${{ secrets.CODECOV_TOKEN }}
59+
working-directory: packages/expreszo
5160

5261
build:
5362
runs-on: ubuntu-latest
@@ -61,19 +70,28 @@ jobs:
6170
uses: actions/setup-node@v4
6271
with:
6372
node-version: 20
64-
cache: 'npm'
73+
cache: 'yarn'
6574

6675
- name: Install dependencies
67-
run: npm ci
76+
run: yarn install --frozen-lockfile
77+
78+
- name: Build (all workspaces)
79+
run: yarn workspaces run build
6880

69-
- name: Build
70-
run: npm run build
81+
- name: Verify package contents (expreszo)
82+
working-directory: packages/expreszo
83+
run: npm pack --dry-run
84+
85+
- name: Verify package contents (expreszo-datetime)
86+
working-directory: packages/expreszo-datetime
87+
run: npm pack --dry-run
7188

72-
- name: Verify package contents
89+
- name: Verify package contents (expreszo-mcp-server)
90+
working-directory: packages/expreszo-mcp-server
7391
run: npm pack --dry-run
7492

7593
- name: Upload build artifacts
7694
uses: actions/upload-artifact@v4
7795
with:
7896
name: dist
79-
path: dist/
97+
path: packages/*/dist/

.github/workflows/deploy-playground.yml

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@ jobs:
2727
uses: actions/setup-node@v4
2828
with:
2929
node-version: 20
30-
cache: 'npm'
30+
cache: 'yarn'
3131

3232
- name: Install dependencies
33-
run: npm ci
33+
run: yarn install --frozen-lockfile
3434

35-
- name: Build UMD bundle
36-
run: npm run build:umd
35+
- name: Build UMD bundle (core)
36+
working-directory: packages/expreszo
37+
run: yarn build:umd
3738

3839
- name: Setup Python
3940
uses: actions/setup-python@v5
@@ -44,17 +45,18 @@ jobs:
4445
run: pip install mkdocs-material==9.7.1
4546

4647
- name: Build documentation
48+
working-directory: packages/expreszo
4749
run: mkdocs build
4850

4951
- name: Prepare playground files
5052
run: |
5153
mkdir -p _site/dist
52-
cp samples/language-service-sample/index.html _site/
53-
cp samples/language-service-sample/app.js _site/
54-
cp samples/language-service-sample/examples.js _site/
55-
cp samples/language-service-sample/styles.css _site/
56-
cp dist/bundle.js _site/dist/
57-
cp -r site _site/docs
54+
cp packages/expreszo/samples/language-service-sample/index.html _site/
55+
cp packages/expreszo/samples/language-service-sample/app.js _site/
56+
cp packages/expreszo/samples/language-service-sample/examples.js _site/
57+
cp packages/expreszo/samples/language-service-sample/styles.css _site/
58+
cp packages/expreszo/dist/bundle.js _site/dist/
59+
cp -r packages/expreszo/site _site/docs
5860
5961
- name: Setup Pages
6062
uses: actions/configure-pages@v5

.github/workflows/publish.yml

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ jobs:
1111
publish:
1212
runs-on: ubuntu-latest
1313

14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
package:
18+
- expreszo
19+
- expreszo-datetime
20+
- expreszo-mcp-server
21+
1422
steps:
1523
- name: Checkout code
1624
uses: actions/checkout@v4
@@ -20,29 +28,32 @@ jobs:
2028
with:
2129
node-version: 20
2230
registry-url: https://registry.npmjs.org/
23-
cache: 'npm'
31+
cache: 'yarn'
2432

2533
- name: Install dependencies
26-
run: npm ci
34+
run: yarn install --frozen-lockfile
2735

28-
- name: Run lint
29-
run: npm run lint
36+
- name: Lint (all workspaces)
37+
run: yarn workspaces run lint
3038

31-
- name: Run type check
32-
run: npm run type-check
39+
- name: Build (all workspaces)
40+
# Must run before type-check; see comment in ci.yml.
41+
run: yarn workspaces run build
3342

34-
- name: Run tests
35-
run: npm test
43+
- name: Type-check (all workspaces)
44+
run: yarn workspaces run type-check
3645

37-
- name: Build
38-
run: npm run build
46+
- name: Test (all workspaces)
47+
run: yarn workspaces run test
3948

40-
- name: Verify package name and version
49+
- name: Show package identity
50+
working-directory: packages/${{ matrix.package }}
4151
run: |
4252
echo "Package: $(node -p "require('./package.json').name")"
4353
echo "Version: $(node -p "require('./package.json').version")"
4454
45-
- name: Publish to npm
55+
- name: Publish ${{ matrix.package }} to npm
56+
working-directory: packages/${{ matrix.package }}
4657
run: npm publish --access public
4758
env:
4859
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

CLAUDE.md

Lines changed: 96 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,126 @@
22

33
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
44

5-
## Commands
5+
## Repository layout
66

7-
```bash
8-
# Build (ESM, UMD, minified UMD)
9-
npm run build
7+
This is a **yarn workspace monorepo** with three published packages:
108

11-
# Run all tests (builds first)
12-
npm test
9+
```
10+
/
11+
├── packages/
12+
│ ├── expreszo/ # @pro-fa/expreszo — core expression evaluator
13+
│ ├── expreszo-datetime/ # @pro-fa/expreszo-datetime — optional Luxon plugin
14+
│ └── expreszo-mcp-server/ # @pro-fa/expreszo-mcp-server — MCP server CLI
15+
├── eslint.config.js # shared eslint config
16+
├── tsconfig.base.json # shared TS compiler options (each package extends this)
17+
└── package.json # private workspace root
18+
```
1319

14-
# Run tests in watch mode
15-
npm run test:watch
20+
The core never depends on Luxon or `@modelcontextprotocol/sdk` — those live only in their respective companion packages.
1621

17-
# Run a single test file
18-
npx vitest run test/core/evaluate.ts
22+
## Commands
1923

20-
# Lint
21-
npm run lint
24+
Run from the **workspace root** to fan out across all three packages:
2225

23-
# Type check
24-
npm run type-check
26+
```bash
27+
yarn install --frozen-lockfile # install all workspaces
28+
yarn workspaces run lint # eslint all packages
29+
yarn workspaces run type-check # TS type-check all packages
30+
yarn workspaces run test # build + vitest in each package
31+
yarn workspaces run build # build dist/ for each package
32+
```
2533

26-
# Coverage (80% threshold required)
27-
npm run coverage
34+
Run from a **single package** for tighter loops:
2835

29-
# Benchmarks
30-
npm run bench
36+
```bash
37+
cd packages/expreszo
38+
yarn test:watch # vitest watch mode
39+
npx vitest run test/core/evaluate.ts # one file
40+
yarn coverage # 80% threshold required
41+
yarn bench # parser benchmarks
3142
```
3243

33-
## Architecture
44+
## Core architecture (`@pro-fa/expreszo`)
3445

35-
**ExpresZo Typescript** is a safe, extensible expression evaluator — a configurable alternative to JavaScript's `eval()`. It uses a **Pratt parser** and an **immutable AST**:
46+
Safe, extensible expression evaluator — a configurable alternative to `eval()`. Uses a **Pratt parser** and an **immutable AST**:
3647

3748
```
3849
Expression string → TokenStream (lexer) → Pratt Parser → AST (immutable) → Evaluator (AST walker) → Result
3950
```
4051

4152
### Key classes
4253

43-
- **`Parser`** (`src/parsing/parser.ts`) — entry point; configurable with custom operators, functions, and variable resolvers; produces `Expression` objects
44-
- **`Expression`** (`src/core/expression.ts`) — compiled expression with methods: `evaluate()`, `simplify()`, `substitute()`, `toString()`, `variables()`, `symbols()`
45-
- **`Evaluator`** (`src/eval/sync-evaluator.ts`, `src/eval/async-evaluator.ts`) — AST walker that evaluates expressions; supports async (Promise) evaluation
46-
- **`TokenStream`** (`src/parsing/token-stream.ts`) — lexer that converts expression strings to tokens
47-
- **`AST Nodes`** (`src/ast/nodes.ts`) — immutable AST node types with a visitor pattern (`src/ast/visitor.ts`)
54+
- **`Parser`** (`packages/expreszo/src/parsing/parser.ts`) — entry point; configurable with custom operators, functions, and variable resolvers; produces `Expression` objects. Carries the runtime `use(plugin)` method.
55+
- **`Expression`** (`packages/expreszo/src/core/expression.ts`) — compiled expression with methods: `evaluate()`, `simplify()`, `substitute()`, `toString()`, `variables()`, `symbols()`.
56+
- **`Evaluator`** (`packages/expreszo/src/eval/sync-evaluator.ts`, `async-evaluator.ts`) — AST walker; supports async (Promise) evaluation.
57+
- **`TokenStream`** (`packages/expreszo/src/parsing/token-stream.ts`) — lexer.
58+
- **`AST Nodes`** (`packages/expreszo/src/ast/nodes.ts`) — immutable AST node types with a visitor pattern (`visitor.ts`).
4859

49-
### Source layout
60+
### Core source layout
5061

5162
```
52-
src/
53-
├── ast/ # AST node types and visitor pattern
54-
├── core/ # Expression, logical operations
55-
├── eval/ # Sync and async evaluators (AST walkers)
56-
├── parsing/ # Pratt parser, TokenStream, token types, parser-state + utils
57-
├── operators/ # Binary (arithmetic, comparison, logical, utility) and unary operators
58-
├── functions/ # Built-ins split by domain: math/, array/, string/, object/, utility/
59-
├── api/ # defineParser and tree-shakeable presets
60-
├── registry/ # Built-in function documentation and descriptors
63+
packages/expreszo/src/
64+
├── ast/ # AST node types and visitor pattern
65+
├── core/ # Expression, logical operations
66+
├── eval/ # Sync and async evaluators (AST walkers)
67+
├── parsing/ # Pratt parser, TokenStream, token types, parser-state + utils
68+
├── operators/ # Binary (arithmetic, comparison, logical, utility) and unary operators
69+
├── functions/ # Built-ins split by domain: math/, array/, string/, object/, utility/
70+
├── api/ # defineParser, presets, Plugin interface
71+
├── registry/ # FunctionDescriptor / OperatorDescriptor catalog
6172
├── language-service/ # IDE completions, hover, diagnostics
62-
├── mcp-server/ # MCP server for AI assistant integration
63-
├── validation/ # Expression validation
64-
├── types/ # Shared TypeScript types and type guards
65-
├── errors/ # Error context helpers
66-
├── utils/ # Shared utilities
67-
└── entries/ # Subpath export entry points
73+
├── validation/ # Expression validation
74+
├── types/ # Shared TypeScript types and type guards
75+
├── errors/ # Error context helpers
76+
├── utils/ # Shared utilities
77+
└── entries/ # Subpath export entry points (./core, ./math, ./string, …)
78+
```
79+
80+
## Plugin API
81+
82+
Companion packages expose a `Plugin` (`packages/expreszo/src/api/plugin.ts`):
83+
84+
```ts
85+
interface Plugin {
86+
readonly name: string;
87+
readonly version?: string;
88+
readonly operators?: readonly OperatorDescriptor[];
89+
readonly functions?: readonly FunctionDescriptor[];
90+
readonly constants?: Readonly<Record<string, Value>>;
91+
}
92+
```
93+
94+
Consumers wire one in with a single call:
95+
96+
```ts
97+
import { defineParser, fullParser } from '@pro-fa/expreszo';
98+
import { dateTimePlugin } from '@pro-fa/expreszo-datetime';
99+
100+
const parser = defineParser({ ...fullParser }).use(dateTimePlugin);
68101
```
69102

70-
### Build targets
103+
`Parser.use()` merges plugin entries into `unaryOps` / `binaryOps` / `ternaryOps` / `functions` / `numericConstants`, throwing on name collision unless `{ override: true }` is passed.
104+
105+
## Adding a new built-in function
106+
107+
Inside `packages/expreszo/src/functions/<category>/`:
108+
109+
1. Write the implementation as a plain function in the appropriate domain file.
110+
2. Export it from `packages/expreszo/src/functions/index.ts`.
111+
3. Add a `FunctionDescriptor` in `packages/expreszo/src/registry/builtin/functions.ts` (`{ name, impl, category, pure, safe, async, docs? }`).
112+
4. Wire the descriptor into the matching preset under `packages/expreszo/src/registry/presets/<category>.ts`.
113+
5. Add tests under `packages/expreszo/test/functions/<category>/`.
114+
115+
For functions that need a heavy dependency (e.g., Luxon), keep them out of core and ship a companion package that exports a `Plugin` (see `packages/expreszo-datetime/` for the canonical example).
116+
117+
## Build targets
118+
119+
Each package's `vite.config.ts` is invoked via `BUILD_TARGET` env var:
71120

72-
Controlled via the `BUILD_TARGET` env var in `vite.config.ts`:
73-
- `esm` (default) — ES module with `.d.ts` declarations → `dist/index.mjs`
74-
- `umd` — universal bundle → `dist/bundle.js`
75-
- `umd-min` — minified UMD → `dist/bundle.min.js`
121+
- `esm` (default) — ES module with `.d.ts` declarations → `dist/<entry>.mjs`
122+
- `umd` — universal bundle (core only) → `dist/bundle.js`
123+
- `umd-min` — minified UMD (core only) → `dist/bundle.min.js`
76124

77-
### Code style
125+
## Code style
78126

79-
ESLint enforces: semicolons, single quotes, 2-space indentation. TypeScript strict mode is on. `@typescript-eslint/no-explicit-any` is relaxed.
127+
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.

0 commit comments

Comments
 (0)