Skip to content

Commit 8a24868

Browse files
committed
ok
1 parent 2e2a32d commit 8a24868

123 files changed

Lines changed: 20838 additions & 17 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.

Makefile

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
.PHONY: all build test wasm extension editor playground website clean
2+
3+
# Build everything
4+
all: extension wasm editor
5+
6+
# Go library
7+
build:
8+
go build ./...
9+
10+
# Run all tests
11+
test:
12+
go test -race -count=1 ./...
13+
cd react && pnpm test
14+
cd playground && pnpm test
15+
16+
# Run e2e tests (requires WASM built + playground dev server)
17+
test-e2e: wasm
18+
cd playground && npx playwright test
19+
20+
# SQLite extension (.dylib on macOS, .so on Linux)
21+
extension:
22+
CGO_ENABLED=1 go build -buildmode=c-shared -ldflags="-s -w" -trimpath \
23+
-o gnata_jsonata$(if $(findstring Darwin,$(shell uname)),.dylib,.so) ./sqlite/
24+
25+
# WASM modules (eval + LSP)
26+
wasm:
27+
GOOS=js GOARCH=wasm go build -ldflags="-s -w" -trimpath -o gnata.wasm ./wasm/
28+
cp "$$(go env GOROOT)/lib/wasm/wasm_exec.js" wasm_exec.js
29+
tinygo build -o gnata-lsp.wasm -no-debug -gc=conservative -target wasm ./editor/
30+
cp "$$(tinygo env TINYGOROOT)/targets/wasm_exec.js" lsp-wasm_exec.js
31+
32+
# CodeMirror npm package
33+
editor:
34+
cd editor/codemirror && pnpm install && pnpm run build
35+
36+
# React widget
37+
react:
38+
cd react && pnpm install && pnpm run build
39+
40+
# Playground (dev server)
41+
playground: wasm
42+
cp gnata.wasm gnata-lsp.wasm wasm_exec.js lsp-wasm_exec.js playground/public/
43+
cd playground && pnpm install && pnpm dev
44+
45+
# Website (dev server)
46+
website:
47+
cp gnata.wasm gnata-lsp.wasm wasm_exec.js lsp-wasm_exec.js website/public/
48+
cd website && pnpm install && pnpm dev
49+
50+
# Website static build
51+
website-build:
52+
cd website && pnpm build
53+
54+
# Install all workspace dependencies
55+
install:
56+
pnpm install
57+
58+
# Clean build artifacts
59+
clean:
60+
rm -f gnata_jsonata.dylib gnata_jsonata.so gnata_jsonata.h
61+
rm -f gnata.wasm gnata-lsp.wasm wasm_exec.js lsp-wasm_exec.js
62+
rm -rf react/dist editor/codemirror/dist website/out

README.md

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# gnata-sqlite
22

3-
Full [JSONata 2.x](https://jsonata.org) implementation in Go — with a loadable SQLite extension, streaming query optimizer, and 85KB WASM LSP.
3+
End-to-end [JSONata 2.x](https://jsonata.org) in Go — from a loadable SQLite extension to a composable React editor with context-aware autocomplete, everything needed to run, write, and edit JSONata expressions.
44

55
[![CI](https://github.com/rbbydotdev/gnata-sqlite/actions/workflows/ci.yml/badge.svg)](https://github.com/rbbydotdev/gnata-sqlite/actions/workflows/ci.yml)
66
[![Go Reference](https://pkg.go.dev/badge/github.com/rbbydotdev/gnata-sqlite.svg)](https://pkg.go.dev/github.com/rbbydotdev/gnata-sqlite)
@@ -9,11 +9,12 @@ Full [JSONata 2.x](https://jsonata.org) implementation in Go — with a loadable
99

1010
## Highlights
1111

12-
- **5M+ eval ops/sec** — two-tier evaluator with GJSON fast path hitting 10M+ ops/sec for simple paths
1312
- **Full JSONata 2.x spec** — paths, wildcards, lambdas, closures, higher-order functions, 50+ stdlib functions. 1,778 conformance tests, 0 failures
14-
- **SQLite extension**`jsonata()`, `jsonata_query()`, `jsonata_each()` as loadable functions with a built-in query planner
15-
- **85KB WASM LSP** — TinyGo-compiled language server for in-browser diagnostics and autocomplete
16-
- **Lock-free streaming**`StreamEvaluator` with schema-keyed plan caching for high-throughput batch workloads
13+
- **SQLite extension**`jsonata()`, `jsonata_query()`, `jsonata_each()` as loadable functions with a built-in query planner that matches native SQL performance
14+
- **React editor widget** — composable hooks and components (`@gnata-sqlite/react`) for embedding a JSONata editor in any app, with autocomplete, hover docs, and live diagnostics
15+
- **85KB WASM LSP** — TinyGo-compiled language server powering in-browser editor features from the same Go codebase as the native LSP
16+
- **Context-aware autocomplete** — the editor evaluates prefix expressions against live data to suggest nested keys, enabling end users to explore and write expressions without knowing the schema upfront
17+
- **5M+ eval ops/sec** — two-tier evaluator with GJSON fast path hitting 10M+ ops/sec for simple paths
1718

1819
## Quick Start
1920

@@ -74,26 +75,28 @@ See [sqlite/README.md](sqlite/README.md) for full docs. Query optimization detai
7475
| `gnata` (root) | Core JSONata 2.x engine — full spec, two-tier eval, streaming |
7576
| [`sqlite/`](sqlite/README.md) | SQLite extension — loadable functions, query planner, mutations |
7677
| [`editor/`](editor/README.md) | CodeMirror 6 language support + TinyGo WASM LSP |
78+
| [`react/`](react/README.md) | Composable React widget — hooks, components, and full playground |
7779

7880
## Building
7981

8082
```bash
81-
# Core library (pure Go)
82-
go build ./...
83-
84-
# SQLite extension (requires CGo)
85-
go build -buildmode=c-shared -o gnata_jsonata.dylib ./sqlite
86-
87-
# WASM LSP (requires TinyGo)
88-
tinygo build -o gnata-lsp.wasm -target wasm ./editor
89-
90-
# CodeMirror npm package
91-
cd editor/codemirror && npm install && npm run build
83+
make all # SQLite extension + WASM modules + CodeMirror package
84+
make extension # SQLite extension only (.dylib / .so)
85+
make wasm # WASM modules (gnata.wasm + gnata-lsp.wasm)
86+
make test # Go tests + React widget tests + playground tests
87+
make playground # Build WASM + start playground dev server
88+
make website # Start docs site dev server
9289
```
9390

91+
See `Makefile` for all targets.
92+
9493
## Playground
9594

96-
Open `playground.html` or visit the [live playground](https://rbbydotdev.github.io/gnata-sqlite/) for interactive testing of JSONata expressions and the SQLite extension.
95+
Visit the [live playground](https://rbbydotdev.github.io/gnata-sqlite/) for interactive testing of JSONata expressions and the SQLite extension. To run locally:
96+
97+
```bash
98+
cd playground && pnpm install && pnpm dev
99+
```
97100

98101
## Contributing
99102

playground/.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

playground/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# React + TypeScript + Vite
2+
3+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4+
5+
Currently, two official plugins are available:
6+
7+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
8+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
9+
10+
## React Compiler
11+
12+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13+
14+
## Expanding the ESLint configuration
15+
16+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
17+
18+
```js
19+
export default defineConfig([
20+
globalIgnores(['dist']),
21+
{
22+
files: ['**/*.{ts,tsx}'],
23+
extends: [
24+
// Other configs...
25+
26+
// Remove tseslint.configs.recommended and replace with this
27+
tseslint.configs.recommendedTypeChecked,
28+
// Alternatively, use this for stricter rules
29+
tseslint.configs.strictTypeChecked,
30+
// Optionally, add this for stylistic rules
31+
tseslint.configs.stylisticTypeChecked,
32+
33+
// Other configs...
34+
],
35+
languageOptions: {
36+
parserOptions: {
37+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
38+
tsconfigRootDir: import.meta.dirname,
39+
},
40+
// other options...
41+
},
42+
},
43+
])
44+
```
45+
46+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
47+
48+
```js
49+
// eslint.config.js
50+
import reactX from 'eslint-plugin-react-x'
51+
import reactDom from 'eslint-plugin-react-dom'
52+
53+
export default defineConfig([
54+
globalIgnores(['dist']),
55+
{
56+
files: ['**/*.{ts,tsx}'],
57+
extends: [
58+
// Other configs...
59+
// Enable lint rules for React
60+
reactX.configs['recommended-typescript'],
61+
// Enable lint rules for React DOM
62+
reactDom.configs.recommended,
63+
],
64+
languageOptions: {
65+
parserOptions: {
66+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
67+
tsconfigRootDir: import.meta.dirname,
68+
},
69+
// other options...
70+
},
71+
},
72+
])
73+
```
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('CodeMirror Editor Features — gnata mode', () => {
4+
test.beforeEach(async ({ page }) => {
5+
await page.goto('/gnata');
6+
// Wait for both eval and LSP WASM to load
7+
await expect(page.locator('.status.ready')).toBeVisible({ timeout: 30000 });
8+
// Extra wait for LSP to fully initialize
9+
await page.waitForTimeout(2000);
10+
});
11+
12+
test('typing in expression editor does not reset cursor', async ({ page }) => {
13+
// Click on expression editor
14+
const exprEditor = page.locator('.gnata-mode-wrapper .cm-content').first();
15+
await exprEditor.click();
16+
17+
// Select all and clear
18+
await page.keyboard.press('Meta+a');
19+
await page.keyboard.press('Backspace');
20+
await page.waitForTimeout(200);
21+
22+
// Type a multi-character expression
23+
await page.keyboard.type('Account.Name', { delay: 50 });
24+
await page.waitForTimeout(300);
25+
26+
// The text should be "Account.Name" (not garbled by cursor resets)
27+
const text = await exprEditor.textContent();
28+
expect(text).toContain('Account.Name');
29+
});
30+
31+
test('autocomplete appears when typing after a dot', async ({ page }) => {
32+
// Click on expression editor
33+
const exprEditor = page.locator('.gnata-mode-wrapper .cm-content').first();
34+
await exprEditor.click();
35+
36+
// Select all and clear
37+
await page.keyboard.press('Meta+a');
38+
await page.keyboard.press('Backspace');
39+
await page.waitForTimeout(200);
40+
41+
// Type "Account." to trigger dot-completion
42+
await page.keyboard.type('Account.', { delay: 80 });
43+
44+
// Wait for autocomplete popup
45+
await page.waitForTimeout(1500);
46+
47+
// Autocomplete tooltip should be visible
48+
const autocomplete = page.locator('.cm-tooltip-autocomplete');
49+
await expect(autocomplete).toBeVisible({ timeout: 5000 });
50+
51+
// Should suggest "Name" and "Order" (from the default input JSON)
52+
const items = await autocomplete.textContent();
53+
expect(items).toContain('Name');
54+
expect(items).toContain('Order');
55+
});
56+
57+
test('hover shows function documentation', async ({ page }) => {
58+
// Load invoice example which uses $sum
59+
await page.getByRole('button', { name: 'Invoice', exact: true }).click();
60+
await page.waitForTimeout(500);
61+
62+
// Hover over $sum in the expression
63+
const exprEditor = page.locator('.gnata-mode-wrapper .cm-content').first();
64+
const box = await exprEditor.boundingBox();
65+
expect(box).not.toBeNull();
66+
67+
// Move mouse to the beginning of the expression where $sum is
68+
await page.mouse.move(box!.x + 20, box!.y + box!.height / 2);
69+
await page.waitForTimeout(2000);
70+
71+
// Hover tooltip should appear with function documentation
72+
const hover = page.locator('.cm-tooltip-hover');
73+
await expect(hover).toBeVisible({ timeout: 5000 });
74+
});
75+
76+
test('diagnostics show red underline on invalid expression', async ({ page }) => {
77+
const exprEditor = page.locator('.gnata-mode-wrapper .cm-content').first();
78+
await exprEditor.click();
79+
80+
// Select all and type an invalid expression
81+
await page.keyboard.press('Meta+a');
82+
await page.keyboard.press('Backspace');
83+
await page.keyboard.type('$sum(', { delay: 50 });
84+
85+
// Wait for linter to run (200ms delay + some buffer)
86+
await page.waitForTimeout(1000);
87+
88+
// Should show a lint error marker or squiggly underline
89+
const lintError = page.locator('.cm-lintRange-error, .cm-diagnostic-error, .cm-lint-marker-error');
90+
await expect(lintError.first()).toBeVisible({ timeout: 5000 });
91+
});
92+
93+
test('example pills change expression and input', async ({ page }) => {
94+
// Click Pipeline example
95+
await page.getByRole('button', { name: 'Pipeline', exact: true }).click();
96+
await page.waitForTimeout(500);
97+
98+
// Expression should contain ~> (pipeline operator)
99+
const expr = page.locator('.gnata-mode-wrapper .cm-content').first();
100+
const exprText = await expr.textContent();
101+
expect(exprText).toContain('~>');
102+
103+
// Input should contain records data
104+
const panels = page.locator('.gnata-mode-wrapper .cm-content');
105+
const inputText = await panels.nth(1).textContent();
106+
expect(inputText).toContain('records');
107+
});
108+
109+
test('result shows green text on successful evaluation', async ({ page }) => {
110+
// Load invoice example
111+
await page.getByRole('button', { name: 'Invoice', exact: true }).click();
112+
// Wait longer for debounced eval (300ms) + StrictMode remount
113+
await page.waitForTimeout(3000);
114+
115+
// Result panel (last cm-content) should have content
116+
const result = page.locator('.gnata-mode-wrapper .cm-content').last();
117+
await expect(result).not.toBeEmpty({ timeout: 5000 });
118+
const resultText = await result.textContent();
119+
expect(resultText).toMatch(/\d/);
120+
});
121+
});

0 commit comments

Comments
 (0)