diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f485020..4283c14 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -33,6 +33,8 @@ jobs: include: - language: javascript-typescript build-mode: none + - language: actions + build-mode: none steps: - name: Checkout diff --git a/.hypatia-ignore b/.hypatia-ignore index fe83feb..ec03c97 100644 --- a/.hypatia-ignore +++ b/.hypatia-ignore @@ -12,9 +12,7 @@ web/Main.res:cicd_rules/banned_language_file web/MainTest.res:cicd_rules/banned_language_file web/Server.res:cicd_rules/banned_language_file -# Frozen archive (playground/experiments/_attic/, see its FROZEN.md): retired -# demo experiments kept for design lineage, not shipped code. The ArangoDB demo -# uses a placeholder DB password ("rootpassword"), not a real credential — this -# is a false-positive that re-fires on every scan. (Disposition keep-frozen vs -# delete is a checkpoint decision; until then, exempt the false-positive.) -playground/experiments/_attic/database-demos/arangodb-demo/queries.js:code_safety/js_hardcoded_secret +# False-positive: rsr_check.rs IS the RSR compliance checker — its "unsafe" +# matches are string literals in its own detector (it scans other files for the +# literal `unsafe {`), not real unsafe blocks. No unsafe code exists here. +crates/jtv-cli/src/rsr_check.rs:code_safety/unsafe_block diff --git a/.machine_readable/6a2/STATE.a2ml b/.machine_readable/6a2/STATE.a2ml index c5530b3..05f280b 100644 --- a/.machine_readable/6a2/STATE.a2ml +++ b/.machine_readable/6a2/STATE.a2ml @@ -7,7 +7,7 @@ project = "julia-the-viper" version = "0.0.1" last-updated = "2026-06-15" status = "active" -session = "2026-06-15 — bookkeeping/tidying consolidation: lake build green (8 libs, 0 sorry/admit/axiom, 0 vacuity, 0 lint warnings); 4 stray branches resolved (2 stale-merged deleted + codeql weekly->monthly cron folded + estate-standardization salvaged: CODEOWNERS + 17 wiki SPDX headers); ADR-0007 gains the graded-comonad/tropical-grade characterization note; PMPL-1.0 vs MPL-2.0 header discrepancy flagged for governance decision (gap-006); number-system-semantics + v2 (c) bridge remain queued" +session = "2026-06-15 — bookkeeping/tidying consolidation: lake build green (8 libs, 0 sorry/admit/axiom, 0 vacuity, 0 lint warnings); 4 stray branches resolved (2 stale-merged flagged for UI deletion — git push --delete returned 403 — + codeql weekly->monthly cron folded + estate-standardization salvaged: CODEOWNERS + 17 wiki SPDX headers); ADR-0007 gains the graded-comonad/tropical-grade characterization note; PMPL-1.0 vs MPL-2.0 header discrepancy flagged for governance decision (gap-006); number-system-semantics + v2 (c) bridge remain queued" [project-context] name = "Julia The Viper" @@ -82,7 +82,8 @@ sessions = [ { date = "2026-06-13", subject = "PR #26 merged: Lean suite repaired (JtvEcho.neg_injective; orphan JtvExtended wired in → 8 libs); Echo a structural type-system gate over BOTH reverse{} and reversible{}->tok (checked first, before type inference); fixed pre-existing failing reverse-block echo test; root-cause Rust-CI revival (rust-ci/coverage hashFiles-in-job-if startup failure since ~05-27); clippy lint cleared; workflow pins+timeouts; *.jtv/*.pata eol=lf" }, { date = "2026-06-13", subject = "PR #27 merged: de-vacuated 8 True-typed believeme theorems (string_not_executable, confluence, no_vulnerable_constructs, no_reverse_joinpoints, dataExpr_no_control, data_evaluation_secure, control_data_noninterference, rev_composition) into real compiled statements; NO-VACUITY + Int-only-scope recorded in capability matrix" }, { date = "2026-06-13", subject = "ADR-0007: addition-only mandate (absolute; ×/÷ generated, not primitive); subtraction = reverse addition (not 2s-complement/not primitive); Harvard-in-von-Neumann AOLD insertability; shortest-path-to-equality + Echo lineage + cross-system routing; additive-algebra → reversibility-tier (group→Safe / cancellative→Neutral / idempotent→Breaking; Echo's own join is idempotent)" }, - { date = "2026-06-15", subject = "Bookkeeping/tidying consolidation: confirmed lake build green (8 libs); cleared all Lean unused-variable lint warnings (_-prefix); resolved 4 stray remote branches (deleted 2 stale-merged changelog/tech-debt whose content is already on main; folded codeql weekly->monthly cron; salvaged CODEOWNERS + 17 wiki SPDX headers from estate-standardization-20260607, dropped its superseded pre-repair Lean drafts + old contractiles); ADR-0007 graded-comonad/tropical-grade characterization note; flagged PMPL-1.0 vs MPL-2.0 discrepancy (gap-006)" } + { date = "2026-06-15", subject = "Bookkeeping/tidying consolidation: confirmed lake build green (8 libs); cleared all Lean unused-variable lint warnings (_-prefix); resolved 4 stray remote branches (flagged 2 stale-merged changelog/tech-debt for UI deletion — content already on main, git 403 blocked programmatic delete; folded codeql weekly->monthly cron; salvaged CODEOWNERS + 17 wiki SPDX headers from estate-standardization-20260607, dropped its superseded pre-repair Lean drafts + old contractiles); ADR-0007 graded-comonad/tropical-grade characterization note; flagged PMPL-1.0 vs MPL-2.0 discrepancy (gap-006)" }, + { date = "2026-06-15", subject = "Security-hygiene clean (post-#33): triaged the Hypatia 67-finding report — informational (--exit-zero, gate passed), none originating from #33. Added CodeQL actions language; DELETED the frozen playground/experiments/_attic archive (17 files incl. stray npm package.json + a Julia demo), clearing both criticals + several highs at the source, with all dangling refs cleaned (deno.json excludes, bot_directives/hypatia.a2ml, .hypatia-ignore, playground README/Justfile); documented rsr_check.rs unsafe_block as a verified false-positive (Hypatia matched the RSR checker's own detection string). Deferred to a deliberate governance-hardening pass: secret-scanner.yml creation, scorecard job-perms, curl|sh install hardening." } ] [design-artefact-locations] diff --git a/.machine_readable/bot_directives/hypatia.a2ml b/.machine_readable/bot_directives/hypatia.a2ml index 9fc70e2..57fc43a 100644 --- a/.machine_readable/bot_directives/hypatia.a2ml +++ b/.machine_readable/bot_directives/hypatia.a2ml @@ -4,7 +4,7 @@ [metadata] repo = "julia-the-viper" -last-updated = "2026-06-14" +last-updated = "2026-06-15" owner = "hyperpolymath" [scanner] @@ -16,13 +16,14 @@ workflow = ".github/workflows/hypatia-scan.yml" # ============================================================ [[accepted-findings]] -rule = "code_safety/js_hardcoded_secret" -path = "playground/experiments/_attic/database-demos/arangodb-demo/queries.js" +rule = "code_safety/unsafe_block" +path = "crates/jtv-cli/src/rsr_check.rs" status = "verified-false-positive" reason = """ -Placeholder DB password ("rootpassword") in the FROZEN demo archive -(playground/experiments/_attic/, see its FROZEN.md) — not a real -credential and not shipped code. Exempted in .hypatia-ignore. +rsr_check.rs IS the RSR compliance checker; its `unsafe` matches are string +literals in its own detector (it scans other files for the literal `unsafe {`), +not real unsafe blocks. No unsafe code exists in this file. Exempted in +.hypatia-ignore. """ [[accepted-findings]] diff --git a/deno.json b/deno.json index adf6666..00c6a96 100644 --- a/deno.json +++ b/deno.json @@ -1,17 +1,11 @@ { "lint": { - "exclude": [ - "playground/experiments/_attic/" - ] + "exclude": [] }, "fmt": { - "exclude": [ - "playground/experiments/_attic/" - ] + "exclude": [] }, "test": { - "exclude": [ - "playground/experiments/_attic/" - ] + "exclude": [] } } diff --git a/playground/Justfile b/playground/Justfile index ff21cb9..a621a29 100644 --- a/playground/Justfile +++ b/playground/Justfile @@ -92,22 +92,6 @@ clean: @find . -type f -name "*.pyc" -delete 2>/dev/null || true @echo "Clean complete" -# ============================================================================== -# LEGACY (QUARANTINED - per ANCHOR scope policy) -# ============================================================================== - -# Run legacy algorithm demos (frozen, no expansion) -_legacy-algorithms: - @echo "WARNING: Legacy code (frozen per ANCHOR scope policy)" - @echo "" - @cd experiments/_attic/algorithms && python3 sorting.py 2>/dev/null || echo "(algorithms not available)" - -# List legacy experiments (frozen) -_legacy-list: - @echo "Legacy Experiments (frozen in _attic/):" - @echo "" - @find experiments/_attic -maxdepth 2 -name "README.md" -exec dirname {} \; 2>/dev/null || echo " (none)" - # ============================================================================== # REPOSITORY MANAGEMENT # ============================================================================== @@ -150,8 +134,8 @@ scope: @echo " - Conformance tests and corpora" @echo "" @echo "LEGACY QUARANTINE:" - @echo " - All non-JTV experiments in experiments/_attic/" - @echo " - No expansion allowed" + @echo " - Non-JTV experiments removed (formerly in experiments/_attic/)" + @echo " - No new non-JTV experiments allowed" @echo "" @echo "FORBIDDEN:" @echo " - General polyglot experiments" diff --git a/playground/README.adoc b/playground/README.adoc index 1d49bc5..a8fb906 100644 --- a/playground/README.adoc +++ b/playground/README.adoc @@ -55,7 +55,6 @@ jtv-playground/ │ ├── tests/corpus/ # Test corpus (valid/invalid) │ ├── repl/ # REPL prototype │ └── tools/ # Development tools -├── experiments/_attic/ # Legacy experiments (frozen) ├── .machine_read/ # Control plane files ├── Justfile # Command interface └── Mustfile # Mandatory checks @@ -75,8 +74,8 @@ Per `.machine_read/ANCHOR.scope-arrest.2026-01-01.scm`: === Legacy (Frozen) -* All non-JTV experiments are in `experiments/_attic/` -* No new features or expansion permitted +* Non-JTV experiments have been removed (formerly quarantined in `experiments/_attic/`) +* No new non-JTV experiments or expansion permitted === Forbidden diff --git a/playground/experiments/_attic/FROZEN.md b/playground/experiments/_attic/FROZEN.md deleted file mode 100644 index 504e129..0000000 --- a/playground/experiments/_attic/FROZEN.md +++ /dev/null @@ -1,43 +0,0 @@ -# Legacy Experiments (FROZEN) - -> **ANCHOR Scope Policy**: These experiments are frozen and may not be expanded. -> Per `.machine_read/ANCHOR.scope-arrest.2026-01-01.scm` - -## Status - -All code in this directory is **LEGACY QUARANTINE**: - -- No new features -- No bug fixes (except security-critical) -- No dependency updates -- No new files or directories - -## Reason - -This repository is now scoped exclusively to **JTV (Julia-the-Viper)** language development. Non-JTV experiments are preserved here for historical reference but are not maintained. - -## Contents - -| Directory | Description | Original Purpose | -|-----------|-------------|------------------| -| `algorithms/` | Algorithm implementations | Python algorithm demos | -| `api-demos/` | REST/WebSocket demos | Node.js API experiments | -| `database-demos/` | Database experiments | SQLite/ArangoDB demos | -| `design-patterns/` | Pattern implementations | Python design patterns | -| `julia-demos/` | Julia language demos | General Julia experiments | -| `python-demos/` | Python utilities | CLI tools, pipelines | -| `utilities/` | Utility libraries | Form validation, etc. | - -## Migration Path - -If any of this code is needed for JTV development: - -1. Copy relevant portions to `jtv/` directory -2. Rewrite in approved language (Julia, ReScript, Rust, Scheme) -3. Do NOT modify code in `_attic/` - -## See Also - -- `.machine_read/ANCHOR.scope-arrest.2026-01-01.scm` - Scope policy -- `.machine_read/LLM_SUPERINTENDENT.scm` - Agent directives -- `jtv/README.adoc` - Active workbench documentation diff --git a/playground/experiments/_attic/algorithms/README.md b/playground/experiments/_attic/algorithms/README.md deleted file mode 100644 index 157f7c7..0000000 --- a/playground/experiments/_attic/algorithms/README.md +++ /dev/null @@ -1,232 +0,0 @@ -# Algorithm Implementations - -Comprehensive collection of classic algorithms with detailed explanations and complexity analysis. - -## Contents - -### 1. Sorting Algorithms (`sorting.py`) - -| Algorithm | Time (Avg) | Time (Worst) | Space | Stable | Notes | -|-----------|------------|--------------|-------|--------|-------| -| Bubble Sort | O(n²) | O(n²) | O(1) | Yes | Simple, good for small/nearly sorted | -| Selection Sort | O(n²) | O(n²) | O(1) | No | Minimizes swaps | -| Insertion Sort | O(n²) | O(n²) | O(1) | Yes | Efficient for small/nearly sorted | -| Merge Sort | O(n log n) | O(n log n) | O(n) | Yes | Divide and conquer, guaranteed O(n log n) | -| Quick Sort | O(n log n) | O(n²) | O(log n) | No | Often fastest in practice | -| Heap Sort | O(n log n) | O(n log n) | O(1) | No | In-place, guaranteed O(n log n) | -| Counting Sort | O(n + k) | O(n + k) | O(k) | Yes | Non-comparison, for integers | -| Radix Sort | O(d(n + k)) | O(d(n + k)) | O(n + k) | Yes | For integers, sorts by digits | -| Bucket Sort | O(n + k) | O(n²) | O(n) | Yes | Good for uniformly distributed data | - -**Usage:** -```python -from sorting import SortingAlgorithms - -arr = [64, 34, 25, 12, 22, 11, 90] -sorted_arr = SortingAlgorithms.merge_sort(arr) -``` - -### 2. Searching Algorithms (`searching.py`) - -| Algorithm | Time (Avg) | Time (Worst) | Space | Requires Sorted | -|-----------|------------|--------------|-------|-----------------| -| Linear Search | O(n) | O(n) | O(1) | No | -| Binary Search | O(log n) | O(log n) | O(1) | Yes | -| Jump Search | O(√n) | O(√n) | O(1) | Yes | -| Interpolation Search | O(log log n) | O(n) | O(1) | Yes (uniform) | -| Exponential Search | O(log n) | O(log n) | O(1) | Yes | -| Ternary Search | O(log₃ n) | O(log₃ n) | O(1) | Yes | -| Fibonacci Search | O(log n) | O(log n) | O(1) | Yes | - -**Special Searches:** -- `find_first_occurrence()`: First index of target -- `find_last_occurrence()`: Last index of target -- `count_occurrences()`: Count of target -- `find_peak_element()`: Local maximum -- `search_rotated_array()`: Search in rotated sorted array - -**Usage:** -```python -from searching import SearchingAlgorithms - -arr = [1, 3, 5, 7, 9, 11, 13, 15, 17] -index = SearchingAlgorithms.binary_search(arr, 11) -``` - -### 3. Dynamic Programming (`dynamic_programming.py`) - -#### Classic Problems - -**Fibonacci Sequence** -- Recursive (exponential) -- Memoization (top-down DP) -- Tabulation (bottom-up DP) -- Space-optimized O(1) - -**Longest Common Subsequence (LCS)** -- O(m×n) time, O(m×n) space -- Find longest subsequence common to two strings - -**0/1 Knapsack** -- O(n×W) time -- Maximum value with weight constraint -- Space-optimized version available - -**Coin Change** -- Minimum coins needed -- Number of ways to make change - -**Longest Increasing Subsequence (LIS)** -- O(n²) DP solution -- O(n log n) binary search solution - -**Edit Distance (Levenshtein)** -- Minimum operations to transform one string to another -- Supports insert, delete, replace - -**Matrix Chain Multiplication** -- O(n³) time -- Minimum scalar multiplications - -**Maximum Subarray Sum (Kadane's)** -- O(n) time, O(1) space -- Maximum sum of contiguous subarray - -**Subset Sum** -- Check if subset exists with given sum -- O(n×target) time - -**Rod Cutting** -- Maximum revenue from cutting rod - -**Usage:** -```python -from dynamic_programming import DynamicProgramming - -# Fibonacci -fib = DynamicProgramming.fibonacci_tabulation(10) - -# Knapsack -weights = [10, 20, 30] -values = [60, 100, 120] -max_value = DynamicProgramming.knapsack_01(weights, values, capacity=50) - -# LCS -length = DynamicProgramming.lcs_dp("AGGTAB", "GXTXAYB") -``` - -## Running the Demos - -Each file includes a demo function showcasing all algorithms: - -```bash -# Sorting algorithms -python sorting.py - -# Searching algorithms -python searching.py - -# Dynamic programming -python dynamic_programming.py -``` - -## Algorithm Selection Guide - -### When to Use Each Sorting Algorithm - -- **Small arrays (n < 50)**: Insertion Sort -- **Nearly sorted data**: Insertion Sort, Bubble Sort -- **Guaranteed O(n log n)**: Merge Sort, Heap Sort -- **Average case performance**: Quick Sort -- **Limited memory**: Heap Sort (in-place) -- **Stable sort needed**: Merge Sort, Bubble Sort -- **Integer data with small range**: Counting Sort, Radix Sort -- **Uniformly distributed data**: Bucket Sort - -### When to Use Each Searching Algorithm - -- **Unsorted data**: Linear Search -- **Sorted data (general)**: Binary Search -- **Sorted + uniform distribution**: Interpolation Search -- **Unknown size/infinite array**: Exponential Search -- **Need simplicity**: Binary Search -- **Large sorted array**: Jump Search - -### Dynamic Programming Problem Patterns - -1. **Optimization**: Maximize/minimize a value - - Knapsack, Rod Cutting, Matrix Chain - -2. **Counting**: Number of ways to achieve something - - Coin Change (ways), Subset Sum - -3. **Decision**: Yes/no feasibility - - Subset Sum (exists) - -4. **Sequence**: Find optimal sequence/subsequence - - LIS, LCS, Edit Distance - -## Complexity Cheat Sheet - -### Time Complexity Classes -- O(1): Constant -- O(log n): Logarithmic -- O(n): Linear -- O(n log n): Linearithmic -- O(n²): Quadratic -- O(n³): Cubic -- O(2ⁿ): Exponential -- O(n!): Factorial - -### Space Complexity -- O(1): In-place -- O(log n): Recursive stack (balanced tree) -- O(n): Linear extra space -- O(n²): Matrix/2D array - -## Testing - -Add unit tests for all algorithms: - -```python -def test_sorting(): - from sorting import SortingAlgorithms - - arr = [64, 34, 25, 12, 22, 11, 90] - expected = [11, 12, 22, 25, 34, 64, 90] - - assert SortingAlgorithms.merge_sort(arr) == expected - assert SortingAlgorithms.quick_sort(arr) == expected -``` - -## Further Reading - -### Books -- "Introduction to Algorithms" (CLRS) -- "Algorithm Design Manual" by Skiena -- "Algorithms" by Sedgewick & Wayne - -### Online Resources -- [LeetCode](https://leetcode.com) - Practice problems -- [GeeksforGeeks](https://www.geeksforgeeks.org) - Explanations -- [Visualgo](https://visualgo.net) - Algorithm visualizations -- [Big-O Cheat Sheet](https://www.bigocheatsheet.com) - -## Contributing - -When adding new algorithms: -1. Include detailed docstrings with complexity analysis -2. Add examples and edge cases -3. Include in demo function -4. Update this README - -## Performance Tips - -1. **Choose the right algorithm** for your data characteristics -2. **Profile before optimizing** - measure, don't guess -3. **Consider trade-offs** - time vs space, simplicity vs performance -4. **Use built-ins** when appropriate - Python's `sorted()` is highly optimized - -## License - -MIT License diff --git a/playground/experiments/_attic/api-demos/rest-api/README.md b/playground/experiments/_attic/api-demos/rest-api/README.md deleted file mode 100644 index 851e8a8..0000000 --- a/playground/experiments/_attic/api-demos/rest-api/README.md +++ /dev/null @@ -1,126 +0,0 @@ -# REST API Demo - -A comprehensive REST API built with Express.js demonstrating best practices. - -## Features - -- RESTful endpoints for users and posts -- CRUD operations -- Query parameters for filtering -- Error handling -- Request logging -- Security middleware (Helmet) -- CORS support -- In-memory data storage - -## Installation - -```bash -npm install -``` - -## Usage - -### Development -```bash -npm run dev -``` - -### Production -```bash -npm start -``` - -### Testing -```bash -npm test -``` - -## API Endpoints - -### Health Check -- `GET /health` - Server health status - -### API Info -- `GET /api` - API documentation - -### Users -- `GET /api/users` - Get all users - - Query params: `?role=admin`, `?search=alice` -- `GET /api/users/:id` - Get user by ID -- `POST /api/users` - Create new user -- `PUT /api/users/:id` - Update user -- `DELETE /api/users/:id` - Delete user -- `GET /api/users/:id/posts` - Get user with their posts - -### Posts -- `GET /api/posts` - Get all posts - - Query params: `?userId=1` -- `GET /api/posts/:id` - Get post by ID -- `POST /api/posts` - Create new post -- `PUT /api/posts/:id` - Update post -- `DELETE /api/posts/:id` - Delete post - -## Example Requests - -### Create User -```bash -curl -X POST http://localhost:3000/api/users \ - -H "Content-Type: application/json" \ - -d '{"name":"John Doe","email":"john@example.com","role":"user"}' -``` - -### Get All Users -```bash -curl http://localhost:3000/api/users -``` - -### Filter Users by Role -```bash -curl http://localhost:3000/api/users?role=admin -``` - -### Create Post -```bash -curl -X POST http://localhost:3000/api/posts \ - -H "Content-Type: application/json" \ - -d '{"userId":1,"title":"My Post","content":"Post content here"}' -``` - -### Update Post -```bash -curl -X PUT http://localhost:3000/api/posts/1 \ - -H "Content-Type: application/json" \ - -d '{"title":"Updated Title"}' -``` - -### Delete Post -```bash -curl -X DELETE http://localhost:3000/api/posts/1 -``` - -## Environment Variables - -Create a `.env` file: - -``` -PORT=3000 -NODE_ENV=development -``` - -## Architecture - -- **Middleware**: Helmet (security), CORS, Morgan (logging) -- **Error Handling**: Centralized error handler -- **Data Storage**: In-memory (can be replaced with database) -- **Validation**: Request body validation -- **Logging**: Request/response logging - -## Next Steps - -- Add database integration (PostgreSQL, MongoDB) -- Implement authentication (JWT) -- Add input validation library (Joi, express-validator) -- Add API documentation (Swagger) -- Implement rate limiting -- Add caching layer diff --git a/playground/experiments/_attic/api-demos/rest-api/package.json b/playground/experiments/_attic/api-demos/rest-api/package.json deleted file mode 100644 index 301aaff..0000000 --- a/playground/experiments/_attic/api-demos/rest-api/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "rest-api-demo", - "version": "1.0.0", - "description": "REST API demo with Express.js", - "main": "server.js", - "scripts": { - "start": "node server.js", - "dev": "nodemon server.js", - "test": "jest" - }, - "keywords": ["express", "rest", "api"], - "author": "", - "license": "MIT", - "dependencies": { - "express": "^4.18.2", - "cors": "^2.8.5", - "helmet": "^7.1.0", - "morgan": "^1.10.0", - "dotenv": "^16.3.1" - }, - "devDependencies": { - "nodemon": "^3.0.2", - "jest": "^29.7.0", - "supertest": "^6.3.3" - } -} diff --git a/playground/experiments/_attic/api-demos/rest-api/server.js b/playground/experiments/_attic/api-demos/rest-api/server.js deleted file mode 100644 index e86c856..0000000 --- a/playground/experiments/_attic/api-demos/rest-api/server.js +++ /dev/null @@ -1,282 +0,0 @@ -const express = require('express'); -const cors = require('cors'); -const helmet = require('helmet'); -const morgan = require('morgan'); -require('dotenv').config(); - -const app = express(); -const PORT = process.env.PORT || 3000; - -// Middleware -app.use(helmet()); -app.use(cors()); -app.use(morgan('dev')); -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); - -// In-memory database (for demo purposes) -let users = [ - { id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'admin' }, - { id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'user' }, - { id: 3, name: 'Charlie Davis', email: 'charlie@example.com', role: 'user' } -]; - -let posts = [ - { id: 1, userId: 1, title: 'First Post', content: 'This is the first post', createdAt: new Date() }, - { id: 2, userId: 2, title: 'Second Post', content: 'This is the second post', createdAt: new Date() } -]; - -let nextUserId = 4; -let nextPostId = 3; - -// Middleware for logging requests -const requestLogger = (req, res, next) => { - console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`); - next(); -}; - -app.use(requestLogger); - -// Error handling middleware -const errorHandler = (err, req, res, next) => { - console.error(err.stack); - res.status(err.status || 500).json({ - error: { - message: err.message || 'Internal Server Error', - status: err.status || 500 - } - }); -}; - -// Routes - -// Health check -app.get('/health', (req, res) => { - res.json({ status: 'healthy', timestamp: new Date().toISOString() }); -}); - -// API Info -app.get('/api', (req, res) => { - res.json({ - name: 'REST API Demo', - version: '1.0.0', - endpoints: { - users: '/api/users', - posts: '/api/posts', - health: '/health' - } - }); -}); - -// USER ROUTES - -// Get all users -app.get('/api/users', (req, res) => { - const { role, search } = req.query; - - let filteredUsers = users; - - if (role) { - filteredUsers = filteredUsers.filter(u => u.role === role); - } - - if (search) { - filteredUsers = filteredUsers.filter(u => - u.name.toLowerCase().includes(search.toLowerCase()) || - u.email.toLowerCase().includes(search.toLowerCase()) - ); - } - - res.json({ - count: filteredUsers.length, - data: filteredUsers - }); -}); - -// Get user by ID -app.get('/api/users/:id', (req, res) => { - const user = users.find(u => u.id === parseInt(req.params.id)); - - if (!user) { - return res.status(404).json({ error: 'User not found' }); - } - - res.json({ data: user }); -}); - -// Create new user -app.post('/api/users', (req, res) => { - const { name, email, role } = req.body; - - if (!name || !email) { - return res.status(400).json({ error: 'Name and email are required' }); - } - - const newUser = { - id: nextUserId++, - name, - email, - role: role || 'user' - }; - - users.push(newUser); - res.status(201).json({ data: newUser }); -}); - -// Update user -app.put('/api/users/:id', (req, res) => { - const userId = parseInt(req.params.id); - const userIndex = users.findIndex(u => u.id === userId); - - if (userIndex === -1) { - return res.status(404).json({ error: 'User not found' }); - } - - const { name, email, role } = req.body; - - users[userIndex] = { - ...users[userIndex], - ...(name && { name }), - ...(email && { email }), - ...(role && { role }) - }; - - res.json({ data: users[userIndex] }); -}); - -// Delete user -app.delete('/api/users/:id', (req, res) => { - const userId = parseInt(req.params.id); - const userIndex = users.findIndex(u => u.id === userId); - - if (userIndex === -1) { - return res.status(404).json({ error: 'User not found' }); - } - - users.splice(userIndex, 1); - res.status(204).send(); -}); - -// POST ROUTES - -// Get all posts -app.get('/api/posts', (req, res) => { - const { userId } = req.query; - - let filteredPosts = posts; - - if (userId) { - filteredPosts = filteredPosts.filter(p => p.userId === parseInt(userId)); - } - - res.json({ - count: filteredPosts.length, - data: filteredPosts - }); -}); - -// Get post by ID -app.get('/api/posts/:id', (req, res) => { - const post = posts.find(p => p.id === parseInt(req.params.id)); - - if (!post) { - return res.status(404).json({ error: 'Post not found' }); - } - - res.json({ data: post }); -}); - -// Create new post -app.post('/api/posts', (req, res) => { - const { userId, title, content } = req.body; - - if (!userId || !title || !content) { - return res.status(400).json({ error: 'userId, title, and content are required' }); - } - - const user = users.find(u => u.id === parseInt(userId)); - if (!user) { - return res.status(400).json({ error: 'Invalid userId' }); - } - - const newPost = { - id: nextPostId++, - userId: parseInt(userId), - title, - content, - createdAt: new Date() - }; - - posts.push(newPost); - res.status(201).json({ data: newPost }); -}); - -// Update post -app.put('/api/posts/:id', (req, res) => { - const postId = parseInt(req.params.id); - const postIndex = posts.findIndex(p => p.id === postId); - - if (postIndex === -1) { - return res.status(404).json({ error: 'Post not found' }); - } - - const { title, content } = req.body; - - posts[postIndex] = { - ...posts[postIndex], - ...(title && { title }), - ...(content && { content }), - updatedAt: new Date() - }; - - res.json({ data: posts[postIndex] }); -}); - -// Delete post -app.delete('/api/posts/:id', (req, res) => { - const postId = parseInt(req.params.id); - const postIndex = posts.findIndex(p => p.id === postId); - - if (postIndex === -1) { - return res.status(404).json({ error: 'Post not found' }); - } - - posts.splice(postIndex, 1); - res.status(204).send(); -}); - -// Get user with their posts -app.get('/api/users/:id/posts', (req, res) => { - const userId = parseInt(req.params.id); - const user = users.find(u => u.id === userId); - - if (!user) { - return res.status(404).json({ error: 'User not found' }); - } - - const userPosts = posts.filter(p => p.userId === userId); - - res.json({ - user, - posts: userPosts, - postCount: userPosts.length - }); -}); - -// 404 handler -app.use((req, res) => { - res.status(404).json({ error: 'Route not found' }); -}); - -// Error handling -app.use(errorHandler); - -// Start server -if (require.main === module) { - app.listen(PORT, () => { - console.log(`Server running on http://localhost:${PORT}`); - console.log(`API documentation: http://localhost:${PORT}/api`); - }); -} - -module.exports = app; diff --git a/playground/experiments/_attic/api-demos/rest-api/server.test.js b/playground/experiments/_attic/api-demos/rest-api/server.test.js deleted file mode 100644 index 3d5993d..0000000 --- a/playground/experiments/_attic/api-demos/rest-api/server.test.js +++ /dev/null @@ -1,156 +0,0 @@ -const request = require('supertest'); -const app = require('./server'); - -describe('REST API Tests', () => { - describe('Health Check', () => { - it('should return healthy status', async () => { - const res = await request(app).get('/health'); - expect(res.status).toBe(200); - expect(res.body).toHaveProperty('status', 'healthy'); - expect(res.body).toHaveProperty('timestamp'); - }); - }); - - describe('API Info', () => { - it('should return API information', async () => { - const res = await request(app).get('/api'); - expect(res.status).toBe(200); - expect(res.body).toHaveProperty('name'); - expect(res.body).toHaveProperty('endpoints'); - }); - }); - - describe('User Endpoints', () => { - it('should get all users', async () => { - const res = await request(app).get('/api/users'); - expect(res.status).toBe(200); - expect(res.body).toHaveProperty('count'); - expect(res.body).toHaveProperty('data'); - expect(Array.isArray(res.body.data)).toBe(true); - }); - - it('should get user by id', async () => { - const res = await request(app).get('/api/users/1'); - expect(res.status).toBe(200); - expect(res.body.data).toHaveProperty('id', 1); - expect(res.body.data).toHaveProperty('name'); - expect(res.body.data).toHaveProperty('email'); - }); - - it('should return 404 for non-existent user', async () => { - const res = await request(app).get('/api/users/9999'); - expect(res.status).toBe(404); - expect(res.body).toHaveProperty('error'); - }); - - it('should create a new user', async () => { - const newUser = { - name: 'Test User', - email: 'test@example.com', - role: 'user' - }; - - const res = await request(app) - .post('/api/users') - .send(newUser); - - expect(res.status).toBe(201); - expect(res.body.data).toHaveProperty('id'); - expect(res.body.data.name).toBe(newUser.name); - expect(res.body.data.email).toBe(newUser.email); - }); - - it('should fail to create user without required fields', async () => { - const res = await request(app) - .post('/api/users') - .send({ name: 'Test' }); - - expect(res.status).toBe(400); - expect(res.body).toHaveProperty('error'); - }); - - it('should filter users by role', async () => { - const res = await request(app).get('/api/users?role=admin'); - expect(res.status).toBe(200); - expect(res.body.data.every(u => u.role === 'admin')).toBe(true); - }); - - it('should search users by name', async () => { - const res = await request(app).get('/api/users?search=alice'); - expect(res.status).toBe(200); - expect(res.body.data.length).toBeGreaterThan(0); - }); - }); - - describe('Post Endpoints', () => { - it('should get all posts', async () => { - const res = await request(app).get('/api/posts'); - expect(res.status).toBe(200); - expect(res.body).toHaveProperty('count'); - expect(res.body).toHaveProperty('data'); - expect(Array.isArray(res.body.data)).toBe(true); - }); - - it('should get post by id', async () => { - const res = await request(app).get('/api/posts/1'); - expect(res.status).toBe(200); - expect(res.body.data).toHaveProperty('id', 1); - expect(res.body.data).toHaveProperty('title'); - expect(res.body.data).toHaveProperty('content'); - }); - - it('should create a new post', async () => { - const newPost = { - userId: 1, - title: 'Test Post', - content: 'This is a test post' - }; - - const res = await request(app) - .post('/api/posts') - .send(newPost); - - expect(res.status).toBe(201); - expect(res.body.data).toHaveProperty('id'); - expect(res.body.data.title).toBe(newPost.title); - }); - - it('should fail to create post with invalid userId', async () => { - const newPost = { - userId: 9999, - title: 'Test Post', - content: 'This is a test post' - }; - - const res = await request(app) - .post('/api/posts') - .send(newPost); - - expect(res.status).toBe(400); - expect(res.body).toHaveProperty('error'); - }); - - it('should filter posts by userId', async () => { - const res = await request(app).get('/api/posts?userId=1'); - expect(res.status).toBe(200); - expect(res.body.data.every(p => p.userId === 1)).toBe(true); - }); - - it('should get user with their posts', async () => { - const res = await request(app).get('/api/users/1/posts'); - expect(res.status).toBe(200); - expect(res.body).toHaveProperty('user'); - expect(res.body).toHaveProperty('posts'); - expect(res.body).toHaveProperty('postCount'); - expect(Array.isArray(res.body.posts)).toBe(true); - }); - }); - - describe('Error Handling', () => { - it('should return 404 for unknown routes', async () => { - const res = await request(app).get('/api/unknown'); - expect(res.status).toBe(404); - expect(res.body).toHaveProperty('error'); - }); - }); -}); diff --git a/playground/experiments/_attic/api-demos/websocket-chat/README.md b/playground/experiments/_attic/api-demos/websocket-chat/README.md deleted file mode 100644 index dd62e93..0000000 --- a/playground/experiments/_attic/api-demos/websocket-chat/README.md +++ /dev/null @@ -1,380 +0,0 @@ -# WebSocket Chat Demo - -A real-time chat application built with Socket.IO and Express.js featuring multiple rooms, typing indicators, and user presence. - -## Features - -### Core Functionality -- **Real-time messaging**: Instant message delivery using WebSockets -- **Multiple chat rooms**: Switch between different rooms dynamically -- **User presence**: See who's online in each room -- **Typing indicators**: Know when someone is typing -- **Message history**: Persistent chat history per room -- **System notifications**: Join/leave announcements - -### Technical Features -- Socket.IO for WebSocket communication -- In-memory storage (easily replaceable with database) -- RESTful API endpoints for stats -- Responsive web interface -- Clean, modern UI - -## Installation - -```bash -npm install -``` - -## Usage - -### Start Server - -```bash -# Development -npm run dev - -# Production -npm start -``` - -The server will start on http://localhost:3001 - -### Access Chat - -Open your browser and navigate to: -``` -http://localhost:3001 -``` - -## API Endpoints - -### GET /api/stats - -Get server statistics: - -```bash -curl http://localhost:3001/api/stats -``` - -Response: -```json -{ - "totalUsers": 5, - "totalRooms": 4, - "rooms": [ - { - "name": "General", - "userCount": 3, - "users": ["Alice", "Bob", "Charlie"], - "createdAt": "2025-11-21T10:00:00.000Z" - } - ], - "uptime": 3600 -} -``` - -### GET /api/rooms - -Get list of all rooms: - -```bash -curl http://localhost:3001/api/rooms -``` - -## Socket.IO Events - -### Client → Server - -| Event | Data | Description | -|-------|------|-------------| -| `join` | `{username, room}` | Join a chat room | -| `send-message` | `{text}` | Send a message | -| `typing` | - | User is typing | -| `stop-typing` | - | User stopped typing | -| `switch-room` | `{room}` | Switch to different room | -| `private-message` | `{to, toId, text}` | Send private message | - -### Server → Client - -| Event | Data | Description | -|-------|------|-------------| -| `message` | `{user, text, timestamp, type}` | New message | -| `room-history` | `Array` | Room message history | -| `users-update` | `{users, count}` | Updated user list | -| `rooms-list` | `Array` | Available rooms | -| `user-typing` | `{username, userId}` | User typing notification | -| `user-stop-typing` | `{userId}` | User stopped typing | -| `private-message` | `{from, to, text, timestamp}` | Private message | - -## Architecture - -### Server Components - -``` -server.js -├── Express HTTP server -├── Socket.IO WebSocket server -├── In-memory data stores -│ ├── users (Map) -│ ├── rooms (Map) -│ └── messageHistory (Array) -└── Event handlers - ├── connection - ├── join - ├── send-message - ├── typing - ├── switch-room - └── disconnect -``` - -### Data Structures - -**User**: -```javascript -{ - id: socket.id, - username: string, - room: string, - joinedAt: Date -} -``` - -**Room**: -```javascript -{ - name: string, - users: Set, - messages: Array, - createdAt: Date -} -``` - -**Message**: -```javascript -{ - id: number, - user: string, - userId: string, - text: string, - timestamp: Date, - type: 'user' | 'system' | 'private' -} -``` - -## Features in Detail - -### Multiple Rooms - -- Default rooms: General, Random, Tech, Gaming -- Users can switch rooms dynamically -- Each room maintains independent message history -- User count per room - -### Typing Indicators - -- Real-time typing notifications -- Automatic timeout after 1 second of inactivity -- Shows individual or multiple users typing - -### Message History - -- Last 100 messages per room stored -- New users receive history on join -- Persists during server uptime - -### User Presence - -- Online users list per room -- Join/leave notifications -- User count tracking - -## Client Features - -### UI Components - -- **Login Screen**: Username and room selection -- **Chat Header**: Room name and user count -- **Sidebar**: Rooms and users list -- **Messages Area**: Chat messages with timestamps -- **Input Area**: Message input with send button -- **Typing Indicator**: Shows who's typing - -### User Experience - -- Clean, modern interface -- Color-coded messages (own vs others) -- System messages for events -- Timestamps for all messages -- Smooth scrolling - -## Extending the Application - -### Add Database Persistence - -Replace in-memory storage with MongoDB: - -```javascript -const mongoose = require('mongoose'); - -const MessageSchema = new mongoose.Schema({ - room: String, - user: String, - text: String, - timestamp: Date -}); - -const Message = mongoose.model('Message', MessageSchema); - -// Save message -socket.on('send-message', async (data) => { - const message = new Message({ - room: user.room, - user: user.username, - text: data.text, - timestamp: new Date() - }); - await message.save(); -}); -``` - -### Add Authentication - -Implement JWT authentication: - -```javascript -const jwt = require('jsonwebtoken'); - -io.use((socket, next) => { - const token = socket.handshake.auth.token; - try { - const decoded = jwt.verify(token, SECRET); - socket.userId = decoded.userId; - next(); - } catch (err) { - next(new Error('Authentication error')); - } -}); -``` - -### Add Private Messaging - -Already implemented! Use: - -```javascript -socket.emit('private-message', { - to: 'username', - toId: 'socket-id', - text: 'Hello!' -}); -``` - -### Add File Sharing - -```javascript -socket.on('send-file', (data) => { - // Handle file upload - const { filename, content, mimeType } = data; - // Store and broadcast file -}); -``` - -### Add Message Reactions - -```javascript -socket.on('add-reaction', (data) => { - const { messageId, emoji } = data; - io.to(room).emit('message-reaction', { - messageId, - emoji, - user: username - }); -}); -``` - -## Security Considerations - -1. **Input Validation**: Validate all user inputs -2. **Rate Limiting**: Prevent message spam -3. **Authentication**: Add user authentication -4. **XSS Protection**: Sanitize message content -5. **CORS**: Configure appropriate CORS settings - -```javascript -// Rate limiting example -const rateLimit = require('express-rate-limit'); - -const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, - max: 100 -}); - -app.use('/api/', limiter); -``` - -## Testing - -Test Socket.IO events: - -```javascript -const io = require('socket.io-client'); -const socket = io('http://localhost:3001'); - -socket.on('connect', () => { - socket.emit('join', { username: 'TestUser', room: 'general' }); -}); - -socket.on('message', (data) => { - console.log('Received:', data); -}); -``` - -## Performance Optimization - -1. **Redis for Scaling**: Use Redis adapter for multiple servers -2. **Message Pagination**: Load messages in chunks -3. **Compression**: Enable Socket.IO compression -4. **Connection Pooling**: Limit concurrent connections - -```javascript -// Redis adapter -const redisAdapter = require('socket.io-redis'); -io.adapter(redisAdapter({ host: 'localhost', port: 6379 })); -``` - -## Deployment - -### Environment Variables - -```env -PORT=3001 -NODE_ENV=production -``` - -### Production Setup - -```bash -# Use process manager -pm2 start server.js -i max - -# Or Docker -docker build -t chat-app . -docker run -p 3001:3001 chat-app -``` - -## Troubleshooting - -### Connection Issues - -- Check firewall settings -- Verify WebSocket support -- Check CORS configuration - -### Message Delays - -- Monitor server resources -- Check network latency -- Consider Redis for scaling - -## License - -MIT License diff --git a/playground/experiments/_attic/api-demos/websocket-chat/package.json b/playground/experiments/_attic/api-demos/websocket-chat/package.json deleted file mode 100644 index 9c6d653..0000000 --- a/playground/experiments/_attic/api-demos/websocket-chat/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "websocket-chat-demo", - "version": "1.0.0", - "description": "Real-time chat application using WebSocket", - "main": "server.js", - "scripts": { - "start": "node server.js", - "dev": "nodemon server.js" - }, - "keywords": ["websocket", "socket.io", "chat", "realtime"], - "author": "", - "license": "MIT", - "dependencies": { - "socket.io": "^4.6.1", - "express": "^4.18.2", - "cors": "^2.8.5" - }, - "devDependencies": { - "nodemon": "^3.0.2" - } -} diff --git a/playground/experiments/_attic/api-demos/websocket-chat/public/index.html b/playground/experiments/_attic/api-demos/websocket-chat/public/index.html deleted file mode 100644 index 7d8e005..0000000 --- a/playground/experiments/_attic/api-demos/websocket-chat/public/index.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - WebSocket Chat Demo - - - -
-

💬 Chat Demo

- - - -
- -
-
-

General

-
0 users
-
-
- -
-
-
-
- - -
-
-
-
- - - - - diff --git a/playground/experiments/_attic/api-demos/websocket-chat/server.js b/playground/experiments/_attic/api-demos/websocket-chat/server.js deleted file mode 100644 index 6949cb0..0000000 --- a/playground/experiments/_attic/api-demos/websocket-chat/server.js +++ /dev/null @@ -1,311 +0,0 @@ -const express = require('express'); -const http = require('http'); -const socketIO = require('socket.io'); -const path = require('path'); - -const app = express(); -const server = http.createServer(app); -const io = socketIO(server, { - cors: { - origin: "*", - methods: ["GET", "POST"] - } -}); - -const PORT = process.env.PORT || 3001; - -// Serve static files -app.use(express.static('public')); - -// In-memory storage -const users = new Map(); -const rooms = new Map(); -const messageHistory = []; -const MAX_HISTORY = 100; - -// Room class -class Room { - constructor(name) { - this.name = name; - this.users = new Set(); - this.messages = []; - this.createdAt = new Date(); - } - - addUser(userId) { - this.users.add(userId); - } - - removeUser(userId) { - this.users.delete(userId); - } - - addMessage(message) { - this.messages.push(message); - if (this.messages.length > MAX_HISTORY) { - this.messages.shift(); - } - } - - getUserCount() { - return this.users.size; - } - - getInfo() { - return { - name: this.name, - userCount: this.getUserCount(), - users: Array.from(this.users).map(id => users.get(id)?.username), - createdAt: this.createdAt - }; - } -} - -// Create default rooms -const defaultRooms = ['General', 'Random', 'Tech', 'Gaming']; -defaultRooms.forEach(name => { - rooms.set(name.toLowerCase(), new Room(name)); -}); - -// Socket.IO connection handling -io.on('connection', (socket) => { - console.log(`New connection: ${socket.id}`); - - // Handle user join - socket.on('join', (data) => { - const { username, room = 'general' } = data; - const roomKey = room.toLowerCase(); - - // Store user info - users.set(socket.id, { - id: socket.id, - username, - room: roomKey, - joinedAt: new Date() - }); - - // Join room - socket.join(roomKey); - - // Add user to room - let roomObj = rooms.get(roomKey); - if (!roomObj) { - roomObj = new Room(room); - rooms.set(roomKey, roomObj); - } - roomObj.addUser(socket.id); - - // Send welcome message - socket.emit('message', { - user: 'System', - text: `Welcome to ${room}, ${username}!`, - timestamp: new Date(), - type: 'system' - }); - - // Send room history - socket.emit('room-history', roomObj.messages); - - // Notify room about new user - socket.to(roomKey).emit('message', { - user: 'System', - text: `${username} has joined the room`, - timestamp: new Date(), - type: 'system' - }); - - // Send updated user list to room - io.to(roomKey).emit('users-update', { - users: Array.from(roomObj.users).map(id => users.get(id)), - count: roomObj.getUserCount() - }); - - // Send available rooms - socket.emit('rooms-list', Array.from(rooms.values()).map(r => r.getInfo())); - - console.log(`${username} joined room: ${room}`); - }); - - // Handle chat messages - socket.on('send-message', (data) => { - const user = users.get(socket.id); - if (!user) return; - - const message = { - id: Date.now(), - user: user.username, - userId: socket.id, - text: data.text, - timestamp: new Date(), - type: 'user' - }; - - // Add to room history - const room = rooms.get(user.room); - if (room) { - room.addMessage(message); - } - - // Broadcast to room - io.to(user.room).emit('message', message); - - console.log(`Message from ${user.username} in ${user.room}: ${data.text}`); - }); - - // Handle typing indicator - socket.on('typing', () => { - const user = users.get(socket.id); - if (!user) return; - - socket.to(user.room).emit('user-typing', { - username: user.username, - userId: socket.id - }); - }); - - socket.on('stop-typing', () => { - const user = users.get(socket.id); - if (!user) return; - - socket.to(user.room).emit('user-stop-typing', { - userId: socket.id - }); - }); - - // Handle room switching - socket.on('switch-room', (data) => { - const user = users.get(socket.id); - if (!user) return; - - const newRoomKey = data.room.toLowerCase(); - const oldRoom = user.room; - - // Leave old room - socket.leave(oldRoom); - const oldRoomObj = rooms.get(oldRoom); - if (oldRoomObj) { - oldRoomObj.removeUser(socket.id); - - // Notify old room - socket.to(oldRoom).emit('message', { - user: 'System', - text: `${user.username} has left the room`, - timestamp: new Date(), - type: 'system' - }); - - // Update old room user list - io.to(oldRoom).emit('users-update', { - users: Array.from(oldRoomObj.users).map(id => users.get(id)), - count: oldRoomObj.getUserCount() - }); - } - - // Join new room - socket.join(newRoomKey); - user.room = newRoomKey; - - let newRoomObj = rooms.get(newRoomKey); - if (!newRoomObj) { - newRoomObj = new Room(data.room); - rooms.set(newRoomKey, newRoomObj); - } - newRoomObj.addUser(socket.id); - - // Send room history - socket.emit('room-history', newRoomObj.messages); - - // Notify new room - socket.to(newRoomKey).emit('message', { - user: 'System', - text: `${user.username} has joined the room`, - timestamp: new Date(), - type: 'system' - }); - - // Update new room user list - io.to(newRoomKey).emit('users-update', { - users: Array.from(newRoomObj.users).map(id => users.get(id)), - count: newRoomObj.getUserCount() - }); - - console.log(`${user.username} switched from ${oldRoom} to ${newRoomKey}`); - }); - - // Handle private messages - socket.on('private-message', (data) => { - const sender = users.get(socket.id); - if (!sender) return; - - const message = { - id: Date.now(), - from: sender.username, - fromId: socket.id, - to: data.to, - text: data.text, - timestamp: new Date(), - type: 'private' - }; - - // Send to recipient - socket.to(data.toId).emit('private-message', message); - - // Send back to sender (for confirmation) - socket.emit('private-message', message); - - console.log(`Private message from ${sender.username} to ${data.to}`); - }); - - // Handle disconnect - socket.on('disconnect', () => { - const user = users.get(socket.id); - if (!user) return; - - // Remove from room - const room = rooms.get(user.room); - if (room) { - room.removeUser(socket.id); - - // Notify room - socket.to(user.room).emit('message', { - user: 'System', - text: `${user.username} has left the room`, - timestamp: new Date(), - type: 'system' - }); - - // Update user list - io.to(user.room).emit('users-update', { - users: Array.from(room.users).map(id => users.get(id)), - count: room.getUserCount() - }); - } - - // Remove user - users.delete(socket.id); - - console.log(`${user.username} disconnected`); - }); -}); - -// API endpoints -app.get('/api/stats', (req, res) => { - res.json({ - totalUsers: users.size, - totalRooms: rooms.size, - rooms: Array.from(rooms.values()).map(r => r.getInfo()), - uptime: process.uptime() - }); -}); - -app.get('/api/rooms', (req, res) => { - res.json(Array.from(rooms.values()).map(r => r.getInfo())); -}); - -// Start server -server.listen(PORT, () => { - console.log(`WebSocket Chat Server running on port ${PORT}`); - console.log(`Stats available at: http://localhost:${PORT}/api/stats`); -}); - -module.exports = { app, server, io }; diff --git a/playground/experiments/_attic/database-demos/arangodb-demo/README.md b/playground/experiments/_attic/database-demos/arangodb-demo/README.md deleted file mode 100644 index 63aa07a..0000000 --- a/playground/experiments/_attic/database-demos/arangodb-demo/README.md +++ /dev/null @@ -1,564 +0,0 @@ -# ArangoDB Multi-Model Database Demo - -Comprehensive demonstration of ArangoDB's multi-model capabilities using AQL (ArangoDB Query Language). - -## Why ArangoDB? - -- **Multi-Model**: Document, Graph, and Key-Value in one database -- **AQL**: Powerful query language combining SQL-like and graph traversal -- **Performance**: Native graph traversal, no joins needed -- **ACID**: Full ACID transactions -- **Scalability**: Horizontal scaling with sharding -- **Flexible Schema**: JSON documents with optional validation - -## Models Supported - -### 1. Document Model -Like MongoDB - store JSON documents in collections. - -### 2. Graph Model -Native graph database with vertices and edges for relationships. - -### 3. Key-Value Model -Simple key-value storage for caching and sessions. - -## Installation - -### Using Podman - -```bash -podman run -d \ - --name arangodb \ - -p 8529:8529 \ - -e ARANGO_ROOT_PASSWORD=rootpassword \ - docker.io/arangodb/arangodb:latest -``` - -### Using ArangoDB Directly - -```bash -# macOS -brew install arangodb - -# Linux (Debian/Ubuntu) -curl -OL https://download.arangodb.com/arangodb39/DEBIAN/Release.key -sudo apt-key add - < Release.key -echo 'deb https://download.arangodb.com/arangodb39/DEBIAN/ /' | sudo tee /etc/apt/sources.list.d/arangodb.list -sudo apt update -sudo apt install arangodb3 -``` - -## Schema Design - -### Collections - -**Document Collections:** -- `users` - User profiles -- `posts` - Blog posts -- `comments` - Post comments -- `tags` - Content tags - -**Edge Collections:** -- `follows` - User → User relationships -- `likes` - User → Post relationships -- `tagged_with` - Post → Tag relationships - -### Graph - -``` -social graph: - - users → follows → users - - users → likes → posts - - posts → tagged_with → tags -``` - -## Running the Demo - -```bash -# Start ArangoDB -podman run -d -p 8529:8529 -e ARANGO_ROOT_PASSWORD=rootpassword arangodb - -# Run demo with Deno -deno run --allow-net queries.js -``` - -## AQL Query Examples - -### Basic Queries - -#### Get all users - -```aql -FOR user IN users - SORT user.name - RETURN user -``` - -#### Filter by condition - -```aql -FOR user IN users - FILTER user.age > 30 - RETURN { name: user.name, age: user.age } -``` - -#### Pagination - -```aql -FOR user IN users - SORT user.name - LIMIT 10, 20 // Skip 10, return 20 - RETURN user -``` - -### Aggregations - -#### Count by group - -```aql -FOR user IN users - COLLECT city = user.city WITH COUNT INTO count - RETURN { city, userCount: count } -``` - -#### Complex aggregation - -```aql -FOR post IN posts - COLLECT author = post.author - AGGREGATE - totalViews = SUM(post.views), - avgViews = AVG(post.views), - postCount = LENGTH(post) - RETURN { - author: DOCUMENT(author).name, - posts: postCount, - totalViews, - avgViews - } -``` - -### Joins (Document Model) - -#### Join with DOCUMENT() - -```aql -FOR post IN posts - RETURN { - title: post.title, - author: DOCUMENT(post.author).name, - views: post.views - } -``` - -#### Subquery joins - -```aql -FOR user IN users - LET userPosts = ( - FOR post IN posts - FILTER post.author == user._id - RETURN post - ) - RETURN { - user: user.name, - postCount: LENGTH(userPosts), - posts: userPosts[*].title - } -``` - -### Graph Traversal - -#### Simple traversal - -```aql -FOR user IN users - FILTER user._key == 'alice' - FOR follower IN 1..1 INBOUND user follows - RETURN follower.name -``` - -#### Multi-level traversal - -```aql -// Followers of followers (2 hops) -FOR user IN users - FILTER user._key == 'alice' - FOR v IN 2..2 INBOUND user follows - RETURN DISTINCT v.name -``` - -#### Shortest path - -```aql -FOR user1 IN users - FILTER user1._key == 'alice' - FOR user2 IN users - FILTER user2._key == 'bob' - FOR v IN OUTBOUND SHORTEST_PATH user1 TO user2 follows - RETURN v.name -``` - -#### All paths - -```aql -FOR user1 IN users - FILTER user1._key == 'alice' - FOR user2 IN users - FILTER user2._key == 'bob' - FOR path IN OUTBOUND K_SHORTEST_PATHS user1 TO user2 follows - LIMIT 5 - RETURN { - path: path.vertices[*].name, - length: LENGTH(path.edges) - } -``` - -#### Pattern matching - -```aql -FOR user IN users - FOR v, e, p IN 1..3 OUTBOUND user follows - FILTER v.city == 'New York' - RETURN { - user: user.name, - connection: v.name, - distance: LENGTH(p.edges) - } -``` - -### Advanced Queries - -#### Full-text search - -```aql -FOR post IN posts - FILTER CONTAINS(LOWER(post.title), LOWER('database')) OR - CONTAINS(LOWER(post.content), LOWER('database')) - RETURN post -``` - -#### Recommendation engine - -```aql -// Posts similar to a given post (by tags) -FOR post IN posts - FILTER post._key == 'post1' - LET postTags = ( - FOR v IN 1..1 OUTBOUND post tagged_with - RETURN v._id - ) - FOR otherPost IN posts - FILTER otherPost._key != 'post1' - LET otherTags = ( - FOR v IN 1..1 OUTBOUND otherPost tagged_with - RETURN v._id - ) - LET similarity = LENGTH(INTERSECTION(postTags, otherTags)) - FILTER similarity > 0 - SORT similarity DESC - LIMIT 5 - RETURN { - title: otherPost.title, - similarityScore: similarity - } -``` - -#### Time-series aggregation - -```aql -FOR post IN posts - COLLECT - month = DATE_FORMAT(post.created_at, '%yyyy-%mm') - AGGREGATE - count = LENGTH(post), - totalViews = SUM(post.views) - SORT month - RETURN { month, posts: count, views: totalViews } -``` - -#### Geospatial queries - -```aql -// Assuming users have lat/lng -FOR user IN users - LET distance = DISTANCE( - user.latitude, user.longitude, - 40.7128, -74.0060 // New York coordinates - ) - FILTER distance < 50000 // Within 50km - SORT distance - RETURN { - name: user.name, - distanceKm: distance / 1000 - } -``` - -### Transactions - -```javascript -const trx = await db.beginTransaction({ - write: ["users", "posts", "follows"] -}); - -try { - // Create user - await trx.step(() => users.save({ - _key: "newuser", - name: "New User" - })); - - // Create post - await trx.step(() => posts.save({ - author: "users/newuser", - title: "First Post" - })); - - // Commit - await trx.commit(); -} catch (error) { - await trx.abort(); - throw error; -} -``` - -## Query Optimization - -### Use Indexes - -```javascript -// Hash index -await collection.ensureIndex({ - type: "hash", - fields: ["email"], - unique: true -}); - -// Skiplist index (for range queries) -await collection.ensureIndex({ - type: "skiplist", - fields: ["age"] -}); - -// Fulltext index -await collection.ensureIndex({ - type: "fulltext", - fields: ["content"], - minLength: 3 -}); - -// Geo index -await collection.ensureIndex({ - type: "geo", - fields: ["location"] -}); -``` - -### Explain Query - -```aql -EXPLAIN -FOR user IN users - FILTER user.age > 30 - RETURN user -``` - -### Query Profiling - -```javascript -const cursor = await db.query(aql` - FOR user IN users RETURN user -`, { profile: true }); - -const profile = cursor.getExtra().profile; -console.log(profile); -``` - -## Best Practices - -### 1. Index Strategy - -```aql -// ✅ Good: Indexed field first -FOR user IN users - FILTER user.email == @email - FILTER user.age > 30 - RETURN user - -// ❌ Bad: Non-indexed field first -FOR user IN users - FILTER user.someField == @value - FILTER user.email == @email - RETURN user -``` - -### 2. Use LIMIT - -```aql -// ✅ Good: Limit early -FOR user IN users - SORT user.created_at DESC - LIMIT 10 - RETURN user - -// ❌ Bad: No limit -FOR user IN users - SORT user.created_at DESC - RETURN user -``` - -### 3. Avoid Unnecessary COLLECT - -```aql -// ✅ Good: Use LENGTH() -FOR user IN users - LET postCount = LENGTH( - FOR post IN posts - FILTER post.author == user._id - RETURN 1 - ) - RETURN { user: user.name, posts: postCount } - -// ❌ Bad: Unnecessary COLLECT -FOR user IN users - LET posts = ( - FOR post IN posts - FILTER post.author == user._id - RETURN post - ) - COLLECT u = user WITH COUNT INTO count - RETURN { user: u.name, posts: count } -``` - -### 4. Use Graph Traversal - -```aql -// ✅ Good: Native graph traversal -FOR v IN 1..3 OUTBOUND @startVertex follows - RETURN v - -// ❌ Bad: Manual joins -FOR user1 IN users - FOR follow1 IN follows - FILTER follow1._from == user1._id - FOR user2 IN users - FILTER user2._id == follow1._to - RETURN user2 -``` - -## Common Patterns - -### Activity Feed - -```aql -FOR user IN users - FILTER user._key == @currentUser - // Get followed users - FOR followed IN 1..1 OUTBOUND user follows - // Get their posts - FOR post IN posts - FILTER post.author == followed._id - SORT post.created_at DESC - LIMIT 20 - RETURN { - post: post, - author: followed.name - } -``` - -### Friend Recommendations - -```aql -FOR user IN users - FILTER user._key == @currentUser - // Friends of friends, excluding current follows - FOR v IN 2..2 OUTBOUND user follows - FILTER v._id != user._id - LET alreadyFollowing = ( - FOR f IN 1..1 OUTBOUND user follows - FILTER f._id == v._id - RETURN 1 - ) - FILTER LENGTH(alreadyFollowing) == 0 - COLLECT suggestion = v WITH COUNT INTO mutualFriends - SORT mutualFriends DESC - LIMIT 10 - RETURN { - user: suggestion.name, - mutualFriends - } -``` - -### Trending Content - -```aql -LET timeWindow = DATE_SUBTRACT(DATE_NOW(), 7, 'days') - -FOR post IN posts - FILTER post.created_at >= timeWindow - LET likes = LENGTH( - FOR like IN likes - FILTER like._to == post._id - RETURN 1 - ) - LET comments = LENGTH( - FOR comment IN comments - FILTER comment.post_id == post._id - RETURN 1 - ) - LET score = (likes * 2) + (comments * 3) + post.views - SORT score DESC - LIMIT 10 - RETURN { - title: post.title, - score, - likes, - comments, - views: post.views - } -``` - -## Web UI - -Access ArangoDB web interface: -``` -http://localhost:8529 -``` - -Credentials: -- Username: `root` -- Password: `rootpassword` - -## CLI Tools - -```bash -# ArangoDB shell -arangosh --server.endpoint tcp://127.0.0.1:8529 - -# Import data -arangoimport --file data.json --collection users --type json - -# Export data -arangoexport --collection users --output-directory ./export - -# Backup -arangodump --output-directory ./backup - -# Restore -arangorestore --input-directory ./backup -``` - -## Performance Tips - -1. **Use appropriate indexes** -2. **Limit result sets** -3. **Avoid deep traversals when possible** -4. **Use COLLECT wisely** -5. **Profile queries in development** -6. **Batch operations when possible** -7. **Use bind parameters** (@variable) -8. **Cache frequently accessed data** - -## License - -MIT License diff --git a/playground/experiments/_attic/database-demos/arangodb-demo/queries.js b/playground/experiments/_attic/database-demos/arangodb-demo/queries.js deleted file mode 100644 index f78681e..0000000 --- a/playground/experiments/_attic/database-demos/arangodb-demo/queries.js +++ /dev/null @@ -1,464 +0,0 @@ -/** - * ArangoDB Multi-Model Database Demo with AQL - * - * Demonstrates: - * - Document model (like MongoDB) - * - Graph model (relationships and traversals) - * - Key-value model - * - AQL (ArangoDB Query Language) - * - Complex joins and aggregations - * - Graph traversal patterns - */ - -import { Database } from "https://deno.land/x/arangojs@8.0.0/mod.ts"; - -class ArangoDBDemo { - constructor(url = "http://localhost:8529") { - this.db = new Database({ - url, - databaseName: "playground", - auth: { username: "root", password: "rootpassword" } - }); - } - - async initialize() { - console.log("🔧 Initializing ArangoDB..."); - - try { - // Create database if doesn't exist - const databases = await this.db.listDatabases(); - if (!databases.includes("playground")) { - await this.db.createDatabase("playground"); - this.db.useDatabase("playground"); - } - - // Create collections - await this.createCollections(); - - // Create graph - await this.createGraph(); - - // Insert sample data - await this.insertSampleData(); - - console.log("✅ Database initialized"); - } catch (error) { - console.error("❌ Initialization error:", error); - throw error; - } - } - - async createCollections() { - const collections = [ - { name: "users", type: "document" }, - { name: "posts", type: "document" }, - { name: "comments", type: "document" }, - { name: "tags", type: "document" }, - { name: "follows", type: "edge" }, - { name: "likes", type: "edge" }, - { name: "tagged_with", type: "edge" } - ]; - - for (const { name, type } of collections) { - const existing = await this.db.listCollections(); - if (!existing.find(c => c.name === name)) { - if (type === "edge") { - await this.db.createEdgeCollection(name); - } else { - await this.db.createCollection(name); - } - console.log(`Created ${type} collection: ${name}`); - } - } - } - - async createGraph() { - const graphs = await this.db.listGraphs(); - - if (!graphs.find(g => g._key === "social")) { - await this.db.createGraph("social", [ - { - collection: "follows", - from: ["users"], - to: ["users"] - }, - { - collection: "likes", - from: ["users"], - to: ["posts"] - }, - { - collection: "tagged_with", - from: ["posts"], - to: ["tags"] - } - ]); - console.log("Created social graph"); - } - } - - async insertSampleData() { - const users = this.db.collection("users"); - const posts = this.db.collection("posts"); - const tags = this.db.collection("tags"); - const follows = this.db.collection("follows"); - const likes = this.db.collection("likes"); - const tagged_with = this.db.collection("tagged_with"); - - // Insert users - const userData = [ - { _key: "alice", name: "Alice Johnson", email: "alice@example.com", age: 28, city: "New York" }, - { _key: "bob", name: "Bob Smith", email: "bob@example.com", age: 35, city: "San Francisco" }, - { _key: "charlie", name: "Charlie Davis", email: "charlie@example.com", age: 42, city: "Austin" }, - { _key: "diana", name: "Diana Miller", email: "diana@example.com", age: 31, city: "Seattle" } - ]; - - for (const user of userData) { - await users.save(user, { overwriteMode: "ignore" }); - } - - // Insert tags - const tagData = [ - { _key: "javascript", name: "JavaScript", category: "programming" }, - { _key: "arangodb", name: "ArangoDB", category: "database" }, - { _key: "webdev", name: "Web Development", category: "general" }, - { _key: "performance", name: "Performance", category: "optimization" } - ]; - - for (const tag of tagData) { - await tags.save(tag, { overwriteMode: "ignore" }); - } - - // Insert posts - const postData = [ - { - _key: "post1", - author: "users/alice", - title: "Getting Started with ArangoDB", - content: "ArangoDB is a multi-model database...", - views: 150, - created_at: new Date("2025-01-15") - }, - { - _key: "post2", - author: "users/bob", - title: "JavaScript Best Practices", - content: "Here are some best practices...", - views: 320, - created_at: new Date("2025-01-18") - }, - { - _key: "post3", - author: "users/alice", - title: "Graph Databases Explained", - content: "Graph databases model data...", - views: 280, - created_at: new Date("2025-01-20") - } - ]; - - for (const post of postData) { - await posts.save(post, { overwriteMode: "ignore" }); - } - - // Create follows relationships - const followsData = [ - { _from: "users/bob", _to: "users/alice" }, - { _from: "users/charlie", _to: "users/alice" }, - { _from: "users/diana", _to: "users/alice" }, - { _from: "users/alice", _to: "users/bob" }, - { _from: "users/charlie", _to: "users/bob" } - ]; - - for (const follow of followsData) { - await follows.save(follow, { overwriteMode: "ignore" }); - } - - // Create likes relationships - const likesData = [ - { _from: "users/bob", _to: "posts/post1", liked_at: new Date() }, - { _from: "users/charlie", _to: "posts/post1", liked_at: new Date() }, - { _from: "users/diana", _to: "posts/post1", liked_at: new Date() }, - { _from: "users/alice", _to: "posts/post2", liked_at: new Date() } - ]; - - for (const like of likesData) { - await likes.save(like, { overwriteMode: "ignore" }); - } - - // Create post-tag relationships - const taggedData = [ - { _from: "posts/post1", _to: "tags/arangodb" }, - { _from: "posts/post1", _to: "tags/webdev" }, - { _from: "posts/post2", _to: "tags/javascript" }, - { _from: "posts/post2", _to: "tags/webdev" }, - { _from: "posts/post3", _to: "tags/arangodb" } - ]; - - for (const tagged of taggedData) { - await tagged_with.save(tagged, { overwriteMode: "ignore" }); - } - - console.log("✅ Sample data inserted"); - } - - // AQL QUERIES - - async getAllUsers() { - console.log("\n📋 Query: Get all users"); - - const query = ` - FOR user IN users - SORT user.name - RETURN user - `; - - const cursor = await this.db.query(query); - const results = await cursor.all(); - console.log(results); - return results; - } - - async getUsersWithFollowerCount() { - console.log("\n📊 Query: Users with follower count"); - - const query = ` - FOR user IN users - LET followerCount = LENGTH( - FOR follow IN follows - FILTER follow._to == user._id - RETURN 1 - ) - RETURN { - name: user.name, - email: user.email, - followers: followerCount - } - `; - - const cursor = await this.db.query(query); - const results = await cursor.all(); - console.log(results); - return results; - } - - async getPopularPosts(minLikes = 2) { - console.log(`\n🔥 Query: Popular posts (min ${minLikes} likes)`); - - const query = ` - FOR post IN posts - LET likeCount = LENGTH( - FOR like IN likes - FILTER like._to == post._id - RETURN 1 - ) - FILTER likeCount >= @minLikes - SORT likeCount DESC - RETURN { - title: post.title, - author: DOCUMENT(post.author).name, - likes: likeCount, - views: post.views - } - `; - - const cursor = await this.db.query(query, { minLikes }); - const results = await cursor.all(); - console.log(results); - return results; - } - - async getPostsWithTags() { - console.log("\n🏷️ Query: Posts with their tags"); - - const query = ` - FOR post IN posts - LET postTags = ( - FOR v IN 1..1 OUTBOUND post tagged_with - RETURN v.name - ) - RETURN { - title: post.title, - author: DOCUMENT(post.author).name, - tags: postTags, - views: post.views - } - `; - - const cursor = await this.db.query(query); - const results = await cursor.all(); - console.log(results); - return results; - } - - async graphTraversal_FollowersOfFollowers(username) { - console.log(`\n🔗 Graph Traversal: Followers of ${username}'s followers`); - - const query = ` - FOR user IN users - FILTER user._key == @username - FOR v, e, p IN 2..2 INBOUND user follows - FILTER v._key != @username - RETURN DISTINCT { - name: v.name, - distance: LENGTH(p.edges) - } - `; - - const cursor = await this.db.query(query, { username }); - const results = await cursor.all(); - console.log(results); - return results; - } - - async graphTraversal_ShortestPath(from, to) { - console.log(`\n🗺️ Shortest path: ${from} → ${to}`); - - const query = ` - FOR user1 IN users - FILTER user1._key == @from - FOR user2 IN users - FILTER user2._key == @to - LET path = ( - FOR v, e IN OUTBOUND SHORTEST_PATH user1 TO user2 follows - RETURN v.name - ) - RETURN path - `; - - const cursor = await this.db.query(query, { from, to }); - const results = await cursor.all(); - console.log(results[0]); - return results[0]; - } - - async complexAggregation() { - console.log("\n📈 Complex Aggregation: User engagement stats"); - - const query = ` - FOR user IN users - LET userPosts = ( - FOR post IN posts - FILTER post.author == user._id - RETURN post - ) - LET totalViews = SUM(userPosts[*].views) - LET totalLikes = SUM( - FOR post IN userPosts - LET likes = LENGTH( - FOR like IN likes - FILTER like._to == post._id - RETURN 1 - ) - RETURN likes - ) - LET followerCount = LENGTH( - FOR follow IN follows - FILTER follow._to == user._id - RETURN 1 - ) - RETURN { - user: user.name, - posts: LENGTH(userPosts), - totalViews: totalViews, - totalLikes: totalLikes, - followers: followerCount, - avgViewsPerPost: totalViews / (LENGTH(userPosts) || 1) - } - `; - - const cursor = await this.db.query(query); - const results = await cursor.all(); - console.log(results); - return results; - } - - async fullTextSearch(searchTerm) { - console.log(`\n🔍 Full-text search: "${searchTerm}"`); - - const query = ` - FOR post IN posts - FILTER CONTAINS(LOWER(post.title), LOWER(@searchTerm)) OR - CONTAINS(LOWER(post.content), LOWER(@searchTerm)) - RETURN { - title: post.title, - author: DOCUMENT(post.author).name, - excerpt: SUBSTRING(post.content, 0, 100) - } - `; - - const cursor = await this.db.query(query, { searchTerm }); - const results = await cursor.all(); - console.log(results); - return results; - } - - async recommendation_SimilarPosts(postKey) { - console.log(`\n💡 Recommendation: Posts similar to ${postKey}`); - - const query = ` - FOR post IN posts - FILTER post._key == @postKey - LET postTags = ( - FOR v IN 1..1 OUTBOUND post tagged_with - RETURN v._id - ) - FOR otherPost IN posts - FILTER otherPost._key != @postKey - LET otherTags = ( - FOR v IN 1..1 OUTBOUND otherPost tagged_with - RETURN v._id - ) - LET commonTags = LENGTH( - INTERSECTION(postTags, otherTags) - ) - FILTER commonTags > 0 - SORT commonTags DESC - LIMIT 3 - RETURN { - title: otherPost.title, - commonTags: commonTags, - views: otherPost.views - } - `; - - const cursor = await this.db.query(query, { postKey }); - const results = await cursor.all(); - console.log(results); - return results; - } -} - -// Demo execution -async function demo() { - console.log("=".repeat(60)); - console.log("ArangoDB Multi-Model Database Demo"); - console.log("=".repeat(60)); - - const arango = new ArangoDBDemo(); - - try { - await arango.initialize(); - - // Run queries - await arango.getAllUsers(); - await arango.getUsersWithFollowerCount(); - await arango.getPopularPosts(2); - await arango.getPostsWithTags(); - await arango.graphTraversal_FollowersOfFollowers("alice"); - await arango.graphTraversal_ShortestPath("alice", "diana"); - await arango.complexAggregation(); - await arango.fullTextSearch("database"); - await arango.recommendation_SimilarPosts("post1"); - - } catch (error) { - console.error("Demo error:", error); - } -} - -// Run if main module -if (import.meta.main) { - await demo(); -} - -export { ArangoDBDemo }; diff --git a/playground/experiments/_attic/database-demos/sqlite-demo/README.md b/playground/experiments/_attic/database-demos/sqlite-demo/README.md deleted file mode 100644 index dd71997..0000000 --- a/playground/experiments/_attic/database-demos/sqlite-demo/README.md +++ /dev/null @@ -1,437 +0,0 @@ -# SQLite Database Demo - -Comprehensive demonstration of SQLite database operations with migrations, repositories, and relationships. - -## Features - -- **Schema Migrations**: Versioned database migrations -- **Repository Pattern**: Clean data access layer -- **Relationships**: One-to-many and many-to-many -- **Transactions**: ACID-compliant operations -- **Indexes**: Optimized queries -- **Aggregations**: Complex queries and statistics - -## Installation - -No external dependencies required (uses Python standard library): - -```bash -python database.py -``` - -## Database Schema - -### Tables - -**users** -- `id`: Primary key -- `username`: Unique username -- `email`: Unique email -- `password_hash`: Hashed password -- `full_name`: Full name (optional) -- `is_active`: Account status -- `created_at`, `updated_at`: Timestamps - -**posts** -- `id`: Primary key -- `user_id`: Foreign key to users -- `title`: Post title -- `content`: Post content -- `status`: draft/published -- `view_count`: Number of views -- `created_at`, `updated_at`: Timestamps - -**tags** -- `id`: Primary key -- `name`: Tag name -- `slug`: URL-friendly slug -- `created_at`: Timestamp - -**post_tags** (Junction table) -- `post_id`: Foreign key to posts -- `tag_id`: Foreign key to tags -- `created_at`: Timestamp - -**comments** -- `id`: Primary key -- `post_id`: Foreign key to posts -- `user_id`: Foreign key to users -- `content`: Comment text -- `created_at`: Timestamp - -### Relationships - -- Users → Posts: One-to-many -- Posts → Tags: Many-to-many (via post_tags) -- Posts → Comments: One-to-many -- Users → Comments: One-to-many - -## Usage - -### Initialize Database - -```python -from database import Database - -db = Database('app.db') -db.initialize() -``` - -### User Operations - -```python -from database import UserRepository - -users = UserRepository(db) - -# Create user -user_id = users.create( - username='john', - email='john@example.com', - password_hash='hashed_password', - full_name='John Doe' -) - -# Find user -user = users.find_by_id(user_id) -user = users.find_by_email('john@example.com') - -# Get all users -all_users = users.find_all(limit=10, offset=0) - -# Update user -users.update(user_id, full_name='John Smith', is_active=True) - -# Delete user -users.delete(user_id) - -# Get user stats -stats = users.get_user_stats(user_id) -print(f"Posts: {stats['post_count']}") -print(f"Comments: {stats['comment_count']}") -print(f"Total views: {stats['total_views']}") -``` - -### Post Operations - -```python -from database import PostRepository - -posts = PostRepository(db) - -# Create post -post_id = posts.create( - user_id=1, - title='My First Post', - content='Hello World!', - status='published' -) - -# Find post -post = posts.find_by_id(post_id) - -# Get all posts -all_posts = posts.find_all(limit=10, offset=0) - -# Get published posts -published = posts.find_all(status='published') - -# Get user's posts -user_posts = posts.find_by_user(user_id) - -# Increment views -posts.increment_views(post_id) - -# Add tags -posts.add_tags(post_id, [tag1_id, tag2_id]) - -# Get post tags -tags = posts.get_post_tags(post_id) -``` - -### Tag Operations - -```python -from database import TagRepository - -tags = TagRepository(db) - -# Create tag -tag_id = tags.create(name='Python', slug='python') - -# Get all tags -all_tags = tags.find_all() - -# Find by slug -tag = tags.find_by_slug('python') - -# Get popular tags -popular = tags.get_popular_tags(limit=10) -for tag in popular: - print(f"{tag['name']}: {tag['post_count']} posts") -``` - -## Migration System - -Migrations are tracked in the `migrations` table: - -```python -class Database: - def run_migrations(self): - # Migration 1: Users table - if not self._migration_executed('create_users_table'): - cursor.execute('CREATE TABLE users ...') - self._record_migration('create_users_table') - - # Migration 2: Posts table - if not self._migration_executed('create_posts_table'): - cursor.execute('CREATE TABLE posts ...') - self._record_migration('create_posts_table') -``` - -### Adding New Migrations - -1. Check if migration executed -2. Run SQL commands -3. Record migration - -```python -if not self._migration_executed('add_user_avatar'): - cursor.execute('ALTER TABLE users ADD COLUMN avatar TEXT') - self._record_migration('add_user_avatar') -``` - -## Indexes - -Optimized queries with indexes: - -```sql -CREATE INDEX idx_users_email ON users(email); -CREATE INDEX idx_users_username ON users(username); -CREATE INDEX idx_posts_user_id ON posts(user_id); -CREATE INDEX idx_posts_status ON posts(status); -CREATE INDEX idx_tags_slug ON tags(slug); -CREATE INDEX idx_comments_post_id ON comments(post_id); -``` - -## Transaction Management - -Using context manager for automatic commit/rollback: - -```python -@contextmanager -def get_connection(self): - conn = sqlite3.connect(self.db_path) - try: - yield conn - conn.commit() # Automatically commit - except Exception: - conn.rollback() # Rollback on error - raise - finally: - conn.close() -``` - -## Complex Queries - -### Get Post with Author - -```python -cursor.execute(''' - SELECT p.*, u.username, u.full_name - FROM posts p - JOIN users u ON p.user_id = u.id - WHERE p.id = ? -''', (post_id,)) -``` - -### Get Popular Tags - -```python -cursor.execute(''' - SELECT t.*, COUNT(pt.post_id) as post_count - FROM tags t - LEFT JOIN post_tags pt ON t.id = pt.tag_id - GROUP BY t.id - ORDER BY post_count DESC - LIMIT ? -''', (limit,)) -``` - -### Get User Statistics - -```python -# Post count -SELECT COUNT(*) FROM posts WHERE user_id = ? - -# Comment count -SELECT COUNT(*) FROM comments WHERE user_id = ? - -# Total views -SELECT SUM(view_count) FROM posts WHERE user_id = ? -``` - -## Repository Pattern - -Clean separation of concerns: - -``` -Database -├── UserRepository -│ ├── create() -│ ├── find_by_id() -│ ├── find_by_email() -│ ├── find_all() -│ ├── update() -│ ├── delete() -│ └── get_user_stats() -├── PostRepository -│ ├── create() -│ ├── find_by_id() -│ ├── find_all() -│ ├── find_by_user() -│ ├── increment_views() -│ ├── add_tags() -│ └── get_post_tags() -└── TagRepository - ├── create() - ├── find_all() - ├── find_by_slug() - └── get_popular_tags() -``` - -## Best Practices - -### 1. Use Parameterized Queries - -✅ **Good**: -```python -cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,)) -``` - -❌ **Bad** (SQL injection risk): -```python -cursor.execute(f'SELECT * FROM users WHERE id = {user_id}') -``` - -### 2. Use Transactions - -```python -with db.get_connection() as conn: - cursor = conn.cursor() - cursor.execute('INSERT INTO users ...') - cursor.execute('INSERT INTO posts ...') - # Automatically commits -``` - -### 3. Use Indexes - -```python -CREATE INDEX idx_users_email ON users(email); -``` - -### 4. Foreign Keys with Cascade - -```python -FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -``` - -## Testing - -```python -import unittest - -class TestUserRepository(unittest.TestCase): - def setUp(self): - self.db = Database(':memory:') # In-memory DB for tests - self.db.initialize() - self.users = UserRepository(self.db) - - def test_create_user(self): - user_id = self.users.create('john', 'john@example.com', 'hash') - self.assertIsNotNone(user_id) - - def test_find_user(self): - user_id = self.users.create('john', 'john@example.com', 'hash') - user = self.users.find_by_id(user_id) - self.assertEqual(user['username'], 'john') -``` - -## Performance Tips - -1. **Use Indexes**: On frequently queried columns -2. **Limit Results**: Use LIMIT and OFFSET for pagination -3. **Analyze Queries**: Use EXPLAIN QUERY PLAN -4. **Batch Operations**: Use executemany() for multiple inserts -5. **Connection Pooling**: Reuse connections when possible - -```python -# Batch insert -cursor.executemany('INSERT INTO users (username, email) VALUES (?, ?)', users_data) -``` - -## Common Patterns - -### Pagination - -```python -def find_all(self, page: int = 1, per_page: int = 10): - offset = (page - 1) * per_page - cursor.execute('SELECT * FROM users LIMIT ? OFFSET ?', (per_page, offset)) -``` - -### Soft Delete - -```python -# Add deleted_at column -cursor.execute('ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP') - -# Soft delete -cursor.execute('UPDATE users SET deleted_at = ? WHERE id = ?', (datetime.now(), user_id)) - -# Query non-deleted -cursor.execute('SELECT * FROM users WHERE deleted_at IS NULL') -``` - -### Full-Text Search - -```python -# Create virtual table -cursor.execute(''' - CREATE VIRTUAL TABLE posts_fts USING fts5(title, content) -''') - -# Search -cursor.execute('SELECT * FROM posts_fts WHERE posts_fts MATCH ?', ('python',)) -``` - -## Extensions - -### Add Timestamps Automatically - -```python -CREATE TRIGGER update_user_timestamp -AFTER UPDATE ON users -BEGIN - UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; -END; -``` - -### Add Audit Trail - -```python -CREATE TABLE audit_log ( - id INTEGER PRIMARY KEY, - table_name TEXT, - record_id INTEGER, - action TEXT, - old_values TEXT, - new_values TEXT, - user_id INTEGER, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -``` - -## License - -MIT License diff --git a/playground/experiments/_attic/design-patterns/README.md b/playground/experiments/_attic/design-patterns/README.md deleted file mode 100644 index 1623f9b..0000000 --- a/playground/experiments/_attic/design-patterns/README.md +++ /dev/null @@ -1,320 +0,0 @@ -# Design Patterns - -Comprehensive implementation of classic Gang of Four (GoF) design patterns in Python. - -## Overview - -Design patterns are reusable solutions to common problems in software design. They represent best practices evolved over time by experienced developers. - -## Pattern Categories - -### Creational Patterns (Object Creation) -Deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. - -### Structural Patterns (Object Composition) -Explain how to assemble objects and classes into larger structures while keeping structures flexible and efficient. - -### Behavioral Patterns (Object Interaction) -Concerned with algorithms and the assignment of responsibilities between objects. - -## Creational Patterns (`creational_patterns.py`) - -| Pattern | Purpose | Use When | -|---------|---------|----------| -| **Singleton** | Ensure only one instance exists | Global configuration, database connections | -| **Factory** | Create objects without specifying exact class | Object type determined at runtime | -| **Abstract Factory** | Create families of related objects | Need consistent object families | -| **Builder** | Construct complex objects step by step | Object has many configuration options | -| **Prototype** | Clone objects instead of creating new | Object creation is expensive | -| **Object Pool** | Reuse objects instead of creating new | Objects are expensive to create/destroy | - -### Usage Examples - -```python -from creational_patterns import * - -# Singleton -db = DatabaseConnection() -db.connect("postgresql://localhost/mydb") - -# Factory -factory = AnimalFactory() -dog = factory.create_animal("dog") - -# Builder -pc = (ComputerBuilder() - .set_cpu("Intel i9") - .set_ram("32GB") - .build()) - -# Prototype -original = Document("Report", "Content", {"author": "John"}) -clone = original.deep_clone() - -# Object Pool -pool = ObjectPool(size=10) -obj = pool.acquire() -pool.release(obj) -``` - -## Structural Patterns (`structural_patterns.py`) - -| Pattern | Purpose | Use When | -|---------|---------|----------| -| **Adapter** | Convert one interface to another | Integrating incompatible interfaces | -| **Bridge** | Separate abstraction from implementation | Both may vary independently | -| **Composite** | Compose objects into tree structures | Need to treat individual and composite objects uniformly | -| **Decorator** | Add responsibilities dynamically | Extending functionality without subclassing | -| **Facade** | Simplified interface to complex subsystem | Hide complexity, provide simple API | -| **Flyweight** | Share common state to reduce memory | Many similar objects needed | -| **Proxy** | Placeholder for another object | Control access, lazy loading, caching | - -### Usage Examples - -```python -from structural_patterns import * - -# Adapter -european_socket = EuropeanSocket() -adapter = SocketAdapter(european_socket) - -# Bridge -tv = TV() -remote = RemoteControl(tv) -remote.toggle_power() - -# Composite -root = Directory("root") -root.add(File("file.txt", 1024)) -root.add(Directory("subfolder")) - -# Decorator -coffee = SimpleCoffee() -fancy = VanillaDecorator(MilkDecorator(coffee)) - -# Facade -computer = ComputerFacade() -computer.start() # Hides complex boot process - -# Flyweight -char = CharacterFactory.get_character('A', 'Arial', 12) - -# Proxy -image = ImageProxy("photo.jpg") # Not loaded yet -image.display() # Loads on first use -``` - -## Behavioral Patterns (`behavioral_patterns.py`) - -| Pattern | Purpose | Use When | -|---------|---------|----------| -| **Strategy** | Define family of interchangeable algorithms | Algorithm varies independently | -| **Observer** | One-to-many dependency notification | State changes need to notify dependents | -| **Command** | Encapsulate request as object | Undo/redo, queuing, logging operations | -| **State** | Alter behavior when state changes | Behavior depends on state | -| **Template Method** | Define algorithm skeleton, defer steps | Common algorithm with varying steps | -| **Iterator** | Sequential access without exposing structure | Traverse collection uniformly | -| **Chain of Responsibility** | Pass request along chain of handlers | Multiple handlers may process request | -| **Mediator** | Encapsulate how objects interact | Reduce coupling between interacting objects | -| **Memento** | Capture and restore object state | Undo/redo, snapshots needed | - -### Usage Examples - -```python -from behavioral_patterns import * - -# Strategy -cart = ShoppingCart() -cart.set_payment_strategy(CreditCardPayment("1234")) -cart.checkout() - -# Observer -subject = Subject() -subject.attach(ConcreteObserver("Observer1")) -subject.set_state("Active") - -# Command -remote = RemoteControl() -remote.execute_command(LightOnCommand(light)) -remote.undo_last() - -# State -doc = DocumentContext() -doc.request() # Transitions through states - -# Template Method -processor = CSVProcessor() -processor.process() # Executes template - -# Iterator -iterator = collection.create_iterator() -while iterator.has_next(): - item = iterator.next() - -# Chain of Responsibility -auth_chain = AuthHandler().set_next(AuthzHandler()) -result = auth_chain.handle(request) - -# Mediator -chatroom = ChatRoom() -user = User("Alice", chatroom) -user.send("Hello!") - -# Memento -editor = TextEditor() -saved = editor.save() -editor.restore(saved) -``` - -## Running the Demos - -Each file includes a comprehensive demo: - -```bash -# Creational patterns -python creational_patterns.py - -# Structural patterns -python structural_patterns.py - -# Behavioral patterns -python behavioral_patterns.py -``` - -## Pattern Selection Guide - -### Creational Patterns - -**Use Singleton when:** -- Exactly one instance needed (config, connection pool) -- Global access point required - -**Use Factory when:** -- Don't know exact types beforehand -- Centralize object creation logic - -**Use Builder when:** -- Object has many optional parameters -- Step-by-step construction needed - -**Use Prototype when:** -- Creating new instances is expensive -- Objects are similar to existing ones - -### Structural Patterns - -**Use Adapter when:** -- Need to use existing class with incompatible interface -- Integrating third-party libraries - -**Use Decorator when:** -- Add responsibilities to individual objects -- Extension by subclassing impractical - -**Use Facade when:** -- Provide simple interface to complex subsystem -- Layer system dependencies - -**Use Proxy when:** -- Control access to object -- Lazy initialization needed -- Remote object access - -### Behavioral Patterns - -**Use Strategy when:** -- Many related classes differ only in behavior -- Need different variants of algorithm -- Algorithm should be hidden from client - -**Use Observer when:** -- Change in one object requires changing others -- Number of dependents unknown or dynamic - -**Use Command when:** -- Parameterize objects with operations -- Queue, log, or support undo operations - -**Use State when:** -- Object behavior depends on state -- State-specific code is substantial - -## Anti-Patterns to Avoid - -1. **Overuse**: Don't force patterns where simple solutions work -2. **Wrong Pattern**: Choose the right pattern for the problem -3. **Premature Optimization**: Don't add complexity early -4. **God Object**: Don't create all-knowing objects -5. **Spaghetti Code**: Maintain clear structure - -## Benefits of Design Patterns - -1. **Proven Solutions**: Battle-tested approaches -2. **Common Vocabulary**: Shared language for developers -3. **Maintainability**: Easier to understand and modify -4. **Scalability**: Flexible architecture -5. **Best Practices**: Codified expert knowledge - -## Pattern Relationships - -### Complementary Patterns - -- **Factory + Singleton**: Create single factory instance -- **Strategy + Factory**: Create strategy objects -- **Composite + Iterator**: Traverse composite structures -- **Command + Memento**: Undo/redo operations -- **Observer + Mediator**: Manage notifications - -### Alternative Patterns - -- **Adapter vs Facade**: Interface conversion vs simplified interface -- **Strategy vs State**: Interchangeable algorithms vs state-dependent behavior -- **Factory vs Abstract Factory**: Single product vs product families - -## Best Practices - -1. **Understand the Problem**: Don't use patterns blindly -2. **Start Simple**: Add patterns when needed -3. **Document Usage**: Explain why pattern was chosen -4. **Test Thoroughly**: Patterns add complexity -5. **Refactor Carefully**: Extract patterns from existing code - -## Further Reading - -### Books -- "Design Patterns" (Gang of Four) - Original patterns book -- "Head First Design Patterns" - Beginner-friendly approach -- "Refactoring to Patterns" - When and how to apply patterns - -### Online Resources -- [Refactoring Guru](https://refactoring.guru/design-patterns) - Excellent visual explanations -- [Source Making](https://sourcemaking.com/design_patterns) - Pattern catalog -- [Python Patterns](https://python-patterns.guide/) - Python-specific patterns - -## Testing - -Test pattern implementations: - -```python -def test_singleton(): - instance1 = Singleton() - instance2 = Singleton() - assert instance1 is instance2 - -def test_factory(): - factory = AnimalFactory() - dog = factory.create_animal("dog") - assert isinstance(dog, Dog) -``` - -## Contributing - -When adding new patterns: -1. Include clear documentation -2. Provide real-world examples -3. Add demo code -4. Update this README - -## License - -MIT License diff --git a/playground/experiments/_attic/julia-demos/data-pipeline/DataPipeline.jl b/playground/experiments/_attic/julia-demos/data-pipeline/DataPipeline.jl deleted file mode 100644 index 731d89e..0000000 --- a/playground/experiments/_attic/julia-demos/data-pipeline/DataPipeline.jl +++ /dev/null @@ -1,377 +0,0 @@ -""" -Julia Data Processing Pipeline - -High-performance ETL pipeline using Julia's ecosystem: -- DataFrames.jl for data manipulation -- CSV.jl for file I/O -- Statistics.jl for statistical analysis -- Dates.jl for temporal operations -- Query.jl for LINQ-style queries - -Performance: 10-100x faster than Python/pandas for large datasets -""" - -module DataPipeline - -using DataFrames -using CSV -using Statistics -using Dates -using Random -using JSON3 -using XLSX - -export Pipeline, load_data, clean_data!, transform_data!, analyze_data, export_data - -struct Pipeline - data::Union{DataFrame, Nothing} - cleaned::Union{DataFrame, Nothing} - transformed::Union{DataFrame, Nothing} - results::Dict{String, Any} - - Pipeline() = new(nothing, nothing, nothing, Dict{String, Any}()) -end - -# Mutable wrapper for pipeline state -mutable struct PipelineState - data::Union{DataFrame, Nothing} - cleaned::Union{DataFrame, Nothing} - transformed::Union{DataFrame, Nothing} - results::Dict{String, Any} -end - -PipelineState() = PipelineState(nothing, nothing, nothing, Dict{String, Any}()) - -""" -Load data from various formats -""" -function load_data(state::PipelineState, filepath::Union{String, Nothing}=nothing) - if isnothing(filepath) - println("📊 No file provided. Generating sample data...") - state.data = generate_sample_data(1000) - return state.data - end - - ext = splitext(filepath)[2] - - if ext == ".csv" - state.data = CSV.read(filepath, DataFrame) - elseif ext in [".xlsx", ".xls"] - state.data = DataFrame(XLSX.readtable(filepath, "Sheet1")...) - elseif ext == ".json" - state.data = JSON3.read(read(filepath, String), DataFrame) - else - error("Unsupported file format: $ext") - end - - println("✅ Loaded $(nrow(state.data)) records from $filepath") - return state.data -end - -""" -Generate sample data for demonstration -""" -function generate_sample_data(n::Int=1000) - Random.seed!(42) - - departments = ["Engineering", "Sales", "Marketing", "HR", "Finance"] - - df = DataFrame( - id = 1:n, - name = ["User_$i" for i in 1:n], - age = rand(18:80, n), - email = ["user$i@example.com" for i in 1:n], - salary = rand(30000:150000, n), - department = rand(departments, n), - join_date = [Date(2020, 1, 1) + Day(i) for i in 1:n], - performance_score = rand(1.0:0.1:5.0, n), - is_active = rand(Bool, n) - ) - - # Introduce missing values - missing_indices = sample(1:n, 50, replace=false) - df[missing_indices, :salary] .= missing - - missing_perf = sample(1:n, 30, replace=false) - df[missing_perf, :performance_score] .= missing - - println("✅ Generated $(nrow(df)) sample records") - return df -end - -""" -Clean data: handle missing values, duplicates, outliers -""" -function clean_data!(state::PipelineState) - isnothing(state.data) && error("No data loaded. Call load_data first.") - - df = copy(state.data) - - println("\n🧹 Cleaning data...") - println("Initial shape: $(size(df))") - - # Remove duplicates - initial_count = nrow(df) - unique!(df) - duplicates_removed = initial_count - nrow(df) - println("Duplicates removed: $duplicates_removed") - - # Handle missing values - missing_before = sum(ismissing.(df)) - - # Fill numeric columns with median - for col in names(df) - if eltype(df[!, col]) <: Union{Missing, Number} - median_val = median(skipmissing(df[!, col])) - df[!, col] = coalesce.(df[!, col], median_val) - end - end - - # Fill categorical columns with mode - for col in names(df) - if eltype(df[!, col]) <: Union{Missing, String} - mode_val = mode(skipmissing(df[!, col])) - df[!, col] = coalesce.(df[!, col], mode_val) - end - end - - missing_after = sum(ismissing.(df)) - println("Missing values handled: $missing_before → $missing_after") - - # Handle outliers using IQR method - for col in names(df) - if eltype(df[!, col]) <: Number - values = df[!, col] - q1 = quantile(values, 0.25) - q3 = quantile(values, 0.75) - iqr = q3 - q1 - lower_bound = q1 - 1.5 * iqr - upper_bound = q3 + 1.5 * iqr - - outliers = sum((values .< lower_bound) .| (values .> upper_bound)) - if outliers > 0 - println("Outliers in $col: $outliers") - # Cap outliers - df[!, col] = clamp.(values, lower_bound, upper_bound) - end - end - end - - println("Final shape: $(size(df))") - - state.cleaned = df - return df -end - -""" -Transform data: feature engineering and derived columns -""" -function transform_data!(state::PipelineState) - isnothing(state.cleaned) && error("No cleaned data. Call clean_data! first.") - - df = copy(state.cleaned) - - println("\n⚡ Transforming data...") - - # Feature engineering - df.years_with_company = [Dates.value(Date(now()) - d) / 365.25 for d in df.join_date] - - df.salary_per_performance = df.salary ./ df.performance_score - - # Age groups - df.age_group = cut(df.age, - [0, 30, 45, 60, 100], - labels=["Young", "Mid-Career", "Senior", "Veteran"]) - - # Performance percentage - df.performance_percentage = (df.performance_score ./ 5.0) .* 100 - - # Salary bands - df.salary_band = cut(df.salary, - [0, 50000, 75000, 100000, 150000], - labels=["Low", "Medium", "High", "Very High"]) - - println("New features created: $(length(names(df)) - length(names(state.cleaned)))") - - state.transformed = df - return df -end - -""" -Analyze data: comprehensive statistical analysis -""" -function analyze_data(state::PipelineState) - isnothing(state.transformed) && error("No transformed data. Call transform_data! first.") - - df = state.transformed - - println("\n📊 Analyzing data...") - - results = Dict{String, Any}() - - # Summary statistics - results["summary_statistics"] = Dict( - "total_records" => nrow(df), - "active_employees" => sum(df.is_active), - "average_age" => mean(df.age), - "average_salary" => mean(df.salary), - "average_performance" => mean(df.performance_score) - ) - - # Department analysis - dept_stats = combine(groupby(df, :department), - :id => length => :count, - :salary => mean => :avg_salary, - :salary => median => :median_salary, - :salary => minimum => :min_salary, - :salary => maximum => :max_salary, - :performance_score => mean => :avg_performance, - :age => mean => :avg_age - ) - results["department_analysis"] = dept_stats - - # Age group analysis - age_stats = combine(groupby(df, :age_group), - :id => length => :count, - :salary => mean => :avg_salary, - :performance_score => mean => :avg_performance - ) - results["age_analysis"] = age_stats - - # Salary band analysis - salary_stats = combine(groupby(df, :salary_band), - :id => length => :count, - :performance_score => mean => :avg_performance, - :years_with_company => mean => :avg_tenure - ) - results["salary_analysis"] = salary_stats - - # Performance analysis - high_performers = filter(row -> row.performance_score >= 4.0, df) - low_performers = filter(row -> row.performance_score < 2.5, df) - - results["performance_analysis"] = Dict( - "high_performers_count" => nrow(high_performers), - "high_performers_avg_salary" => mean(high_performers.salary), - "low_performers_count" => nrow(low_performers), - "low_performers_avg_salary" => nrow(low_performers) > 0 ? mean(low_performers.salary) : 0.0 - ) - - # Correlation analysis - numeric_cols = [:salary, :age, :performance_score, :years_with_company] - salary_corr = Dict() - for col in numeric_cols - if col != :salary - salary_corr[string(col)] = cor(df.salary, df[!, col]) - end - end - results["salary_correlations"] = salary_corr - - state.results = results - return results -end - -""" -Export processed data and results -""" -function export_data(state::PipelineState, output_dir::String="output") - mkpath(output_dir) - - println("\n💾 Exporting data to $output_dir...") - - # Export transformed data - if !isnothing(state.transformed) - CSV.write(joinpath(output_dir, "transformed_data.csv"), state.transformed) - println("✅ Exported CSV: $output_dir/transformed_data.csv") - - # XLSX export requires XLSX.jl - try - XLSX.writetable(joinpath(output_dir, "transformed_data.xlsx"), - collect(DataFrames.eachcol(state.transformed)), - DataFrames.names(state.transformed)) - println("✅ Exported Excel: $output_dir/transformed_data.xlsx") - catch e - println("⚠️ Excel export failed: $e") - end - end - - # Export analysis results - if !isempty(state.results) - results_json = JSON3.write(state.results, 2) - write(joinpath(output_dir, "analysis_results.json"), results_json) - println("✅ Exported JSON: $output_dir/analysis_results.json") - end -end - -""" -Run complete pipeline -""" -function run_pipeline(filepath::Union{String, Nothing}=nothing; export_results::Bool=true) - println("="^60) - println("Julia Data Processing Pipeline") - println("="^60) - - state = PipelineState() - - # Execute pipeline - load_data(state, filepath) - clean_data!(state) - transform_data!(state) - results = analyze_data(state) - - if export_results - export_data(state) - end - - println("\n" * "="^60) - println("Pipeline completed successfully!") - println("="^60) - - return state, results -end - -# Helper function for categorical binning -function cut(x::AbstractVector, breaks::Vector, labels::Vector{String}) - n = length(breaks) - 1 - result = Vector{String}(undef, length(x)) - - for (i, val) in enumerate(x) - for j in 1:n - if breaks[j] <= val < breaks[j+1] || (j == n && val == breaks[j+1]) - result[i] = labels[j] - break - end - end - end - - return result -end - -end # module - -# Demo execution -using .DataPipeline - -function demo() - state, results = run_pipeline() - - println("\n--- Summary Statistics ---") - for (key, value) in results["summary_statistics"] - println("$key: $value") - end - - println("\n--- Department Analysis ---") - println(results["department_analysis"]) - - println("\n--- Performance Insights ---") - perf = results["performance_analysis"] - println("High performers: $(perf["high_performers_count"]) " * - "(Avg salary: \$$(round(perf["high_performers_avg_salary"], digits=2)))") - println("Low performers: $(perf["low_performers_count"]) " * - "(Avg salary: \$$(round(perf["low_performers_avg_salary"], digits=2)))") -end - -# Run if executed directly -if abspath(PROGRAM_FILE) == @__FILE__ - demo() -end diff --git a/playground/experiments/_attic/utilities/form-validation/README.md b/playground/experiments/_attic/utilities/form-validation/README.md deleted file mode 100644 index ff5f0a2..0000000 --- a/playground/experiments/_attic/utilities/form-validation/README.md +++ /dev/null @@ -1,559 +0,0 @@ -# Form Validation Library - -A comprehensive, chainable form validation library for JavaScript with support for sync/async validation, cross-field validation, and conditional rules. - -## Features - -- **Chainable API**: Fluent interface for defining validation rules -- **Built-in Validators**: Email, URL, phone, credit card, etc. -- **Custom Validators**: Add your own validation logic -- **Async Validation**: Support for asynchronous validators (API calls, database lookups) -- **Cross-field Validation**: Validate fields based on other field values -- **Conditional Validation**: Apply rules conditionally -- **Detailed Error Messages**: Custom, descriptive error messages -- **Zero Dependencies**: Pure JavaScript, no external libraries - -## Installation - -```bash -# Copy validator.js to your project -cp validator.js your-project/ -``` - -## Quick Start - -```javascript -const { FormValidator } = require('./validator'); - -// Create validator -const validator = new FormValidator(); - -// Define rules -validator.field('email') - .required() - .email(); - -validator.field('password') - .required() - .minLength(8); - -// Validate -const result = await validator.validate({ - email: 'user@example.com', - password: 'securepass123' -}); - -if (result.isValid) { - console.log('Form is valid!'); -} else { - console.log('Errors:', result.errors); -} -``` - -## Built-in Validators - -### Required -```javascript -validator.field('username').required('Username is required'); -``` - -### String Length -```javascript -validator.field('username') - .minLength(3, 'At least 3 characters') - .maxLength(20, 'At most 20 characters'); -``` - -### Numeric Range -```javascript -validator.field('age') - .min(18, 'Must be 18 or older') - .max(100, 'Invalid age'); -``` - -### Email -```javascript -validator.field('email').email('Invalid email format'); -``` - -### URL -```javascript -validator.field('website').url('Invalid URL'); -``` - -### Phone Number -```javascript -validator.field('phone').phone('Invalid phone number'); -``` - -### Pattern Matching -```javascript -validator.field('zipCode') - .pattern(/^\d{5}$/, 'Must be 5 digits'); -``` - -### Alphanumeric -```javascript -validator.field('username').alphanumeric(); -``` - -### Numeric Only -```javascript -validator.field('code').numeric(); -``` - -### Letters Only -```javascript -validator.field('name').alpha(); -``` - -### One Of -```javascript -validator.field('country') - .oneOf(['US', 'CA', 'UK', 'AU'], 'Invalid country'); -``` - -### Matches Other Field -```javascript -validator.field('confirmPassword') - .matches('password', 'Passwords must match'); -``` - -## Advanced Validators - -### Strong Password -```javascript -const Validators = require('./validator').Validators; - -validator.field('password') - .required() - .minLength(8) - .custom( - Validators.strongPassword, - 'Password must include uppercase, lowercase, number, and special character' - ); -``` - -### Credit Card (Luhn Algorithm) -```javascript -validator.field('cardNumber') - .custom(Validators.creditCard, 'Invalid credit card number'); -``` - -### Date Validation -```javascript -// Valid date -validator.field('birthdate') - .custom(Validators.date, 'Invalid date'); - -// Future date -validator.field('appointmentDate') - .custom(Validators.futureDate, 'Date must be in the future'); - -// Past date -validator.field('birthdate') - .custom(Validators.pastDate, 'Birthdate must be in the past'); - -// Minimum age -validator.field('birthdate') - .custom(Validators.age(18), 'Must be at least 18 years old'); -``` - -### Username -```javascript -validator.field('username') - .custom(Validators.username, 'Username must be 3-20 characters, letters/numbers/underscore/hyphen only'); -``` - -### Zip Code -```javascript -validator.field('zipCode') - .custom(Validators.zipCode('US'), 'Invalid US zip code'); - -validator.field('postalCode') - .custom(Validators.zipCode('UK'), 'Invalid UK postal code'); -``` - -## Custom Validators - -### Synchronous Custom Validator -```javascript -validator.field('username') - .custom( - (value) => !value.includes('admin'), - 'Username cannot contain "admin"' - ); -``` - -### Asynchronous Custom Validator -```javascript -validator.field('username') - .async( - async (value) => { - const response = await fetch(`/api/check-username/${value}`); - const data = await response.json(); - return data.available; - }, - 'Username is already taken' - ); -``` - -## Conditional Validation - -```javascript -validator.field('companyName') - .when( - (values) => values.accountType === 'business', - (value) => value && value.length >= 3 - ); - -// companyName is only required when accountType is 'business' -``` - -## Cross-Field Validation - -```javascript -validator.field('confirmPassword') - .required() - .matches('password', 'Passwords must match'); - -validator.field('endDate') - .custom( - (value, allValues) => { - return new Date(value) > new Date(allValues.startDate); - }, - 'End date must be after start date' - ); -``` - -## Validation Results - -```javascript -const result = await validator.validate(formData); - -console.log(result); -// { -// isValid: false, -// fields: { -// email: { isValid: true, errors: [] }, -// password: { isValid: false, errors: ['Password is too short'] } -// }, -// errors: { -// password: ['Password is too short'] -// } -// } -``` - -## Single Field Validation - -```javascript -const result = await validator.validateField('email', 'user@example.com'); - -if (result.isValid) { - console.log('Email is valid'); -} else { - console.log('Errors:', result.errors); -} -``` - -## Complete Example - -```javascript -const validator = new FormValidator(); - -// User registration form -validator.field('username') - .required('Username is required') - .minLength(3, 'Username must be at least 3 characters') - .maxLength(20, 'Username must be at most 20 characters') - .alphanumeric('Username can only contain letters and numbers') - .async( - async (value) => { - const response = await fetch(`/api/check-username/${value}`); - return response.ok; - }, - 'Username is already taken' - ); - -validator.field('email') - .required('Email is required') - .email('Invalid email format') - .async( - async (value) => { - const response = await fetch(`/api/check-email/${value}`); - return response.ok; - }, - 'Email is already registered' - ); - -validator.field('password') - .required('Password is required') - .minLength(8, 'Password must be at least 8 characters') - .custom( - Validators.strongPassword, - 'Password must include uppercase, lowercase, number, and special character' - ); - -validator.field('confirmPassword') - .required('Please confirm your password') - .matches('password', 'Passwords do not match'); - -validator.field('age') - .required('Age is required') - .numeric('Age must be a number') - .min(18, 'You must be at least 18 years old') - .max(120, 'Invalid age'); - -validator.field('website') - .url('Invalid website URL'); - -validator.field('phone') - .required('Phone number is required') - .phone('Invalid phone number format'); - -validator.field('termsAccepted') - .custom( - (value) => value === true || value === 'true', - 'You must accept the terms and conditions' - ); - -// Validate form -const formData = { - username: 'john123', - email: 'john@example.com', - password: 'SecurePass123!', - confirmPassword: 'SecurePass123!', - age: '25', - website: 'https://example.com', - phone: '+1-555-123-4567', - termsAccepted: true -}; - -const result = await validator.validate(formData); - -if (result.isValid) { - // Submit form - await submitForm(formData); -} else { - // Display errors - displayErrors(result.errors); -} -``` - -## HTML Form Integration - -```html - - - - Form Validation Demo - - -
-
- - - -
- -
- - - -
- -
- - - -
- -
- - - -
- - -
- - - - - -``` - -## React Integration - -```jsx -import { useState } from 'react'; -import { FormValidator } from './validator'; - -const validator = new FormValidator(); - -validator.field('email').required().email(); -validator.field('password').required().minLength(8); - -function LoginForm() { - const [values, setValues] = useState({ email: '', password: '' }); - const [errors, setErrors] = useState({}); - - const handleChange = (e) => { - setValues({ ...values, [e.target.name]: e.target.value }); - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - const result = await validator.validate(values); - - if (result.isValid) { - // Submit form - console.log('Valid!', values); - } else { - setErrors(result.errors); - } - }; - - return ( -
-
- - {errors.email && {errors.email[0]}} -
- -
- - {errors.password && {errors.password[0]}} -
- - -
- ); -} -``` - -## Testing - -```javascript -const { FormValidator } = require('./validator'); - -describe('FormValidator', () => { - let validator; - - beforeEach(() => { - validator = new FormValidator(); - }); - - test('validates required field', async () => { - validator.field('username').required(); - - const result = await validator.validate({ username: '' }); - expect(result.isValid).toBe(false); - expect(result.errors.username).toBeDefined(); - }); - - test('validates email format', async () => { - validator.field('email').email(); - - const valid = await validator.validate({ email: 'user@example.com' }); - expect(valid.isValid).toBe(true); - - const invalid = await validator.validate({ email: 'invalid' }); - expect(invalid.isValid).toBe(false); - }); - - test('validates password match', async () => { - validator.field('confirmPassword').matches('password'); - - const match = await validator.validate({ - password: 'secret', - confirmPassword: 'secret' - }); - expect(match.isValid).toBe(true); - - const noMatch = await validator.validate({ - password: 'secret', - confirmPassword: 'different' - }); - expect(noMatch.isValid).toBe(false); - }); -}); -``` - -## License - -MIT License diff --git a/playground/experiments/_attic/utilities/form-validation/validator.js b/playground/experiments/_attic/utilities/form-validation/validator.js deleted file mode 100644 index b89a75b..0000000 --- a/playground/experiments/_attic/utilities/form-validation/validator.js +++ /dev/null @@ -1,455 +0,0 @@ -/** - * Comprehensive Form Validation Library - * - * Features: - * - Built-in validators (email, URL, phone, etc.) - * - Custom validators - * - Async validation - * - Conditional validation - * - Cross-field validation - * - Custom error messages - */ - -class ValidationRule { - constructor(validator, message) { - this.validator = validator; - this.message = message; - } - - async validate(value, allValues = {}) { - const isValid = await this.validator(value, allValues); - return { - isValid, - message: isValid ? null : this.message - }; - } -} - -class FieldValidator { - constructor(fieldName) { - this.fieldName = fieldName; - this.rules = []; - this.isRequired = false; - } - - required(message = `${this.fieldName} is required`) { - this.isRequired = true; - this.rules.push(new ValidationRule( - (value) => value !== undefined && value !== null && value !== '', - message - )); - return this; - } - - min(minValue, message = `${this.fieldName} must be at least ${minValue}`) { - this.rules.push(new ValidationRule( - (value) => !value || value >= minValue, - message - )); - return this; - } - - max(maxValue, message = `${this.fieldName} must be at most ${maxValue}`) { - this.rules.push(new ValidationRule( - (value) => !value || value <= maxValue, - message - )); - return this; - } - - minLength(minLength, message = `${this.fieldName} must be at least ${minLength} characters`) { - this.rules.push(new ValidationRule( - (value) => !value || value.length >= minLength, - message - )); - return this; - } - - maxLength(maxLength, message = `${this.fieldName} must be at most ${maxLength} characters`) { - this.rules.push(new ValidationRule( - (value) => !value || value.length <= maxLength, - message - )); - return this; - } - - email(message = `${this.fieldName} must be a valid email`) { - this.rules.push(new ValidationRule( - (value) => { - if (!value) return true; - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(value); - }, - message - )); - return this; - } - - url(message = `${this.fieldName} must be a valid URL`) { - this.rules.push(new ValidationRule( - (value) => { - if (!value) return true; - try { - new URL(value); - return true; - } catch { - return false; - } - }, - message - )); - return this; - } - - phone(message = `${this.fieldName} must be a valid phone number`) { - this.rules.push(new ValidationRule( - (value) => { - if (!value) return true; - const phoneRegex = /^\+?[\d\s\-()]+$/; - return phoneRegex.test(value) && value.replace(/\D/g, '').length >= 10; - }, - message - )); - return this; - } - - pattern(regex, message = `${this.fieldName} format is invalid`) { - this.rules.push(new ValidationRule( - (value) => !value || regex.test(value), - message - )); - return this; - } - - alphanumeric(message = `${this.fieldName} must contain only letters and numbers`) { - return this.pattern(/^[a-zA-Z0-9]+$/, message); - } - - numeric(message = `${this.fieldName} must contain only numbers`) { - return this.pattern(/^\d+$/, message); - } - - alpha(message = `${this.fieldName} must contain only letters`) { - return this.pattern(/^[a-zA-Z]+$/, message); - } - - oneOf(values, message = `${this.fieldName} must be one of: ${values.join(', ')}`) { - this.rules.push(new ValidationRule( - (value) => !value || values.includes(value), - message - )); - return this; - } - - matches(fieldName, message = `${this.fieldName} must match ${fieldName}`) { - this.rules.push(new ValidationRule( - (value, allValues) => !value || value === allValues[fieldName], - message - )); - return this; - } - - custom(validator, message = `${this.fieldName} is invalid`) { - this.rules.push(new ValidationRule(validator, message)); - return this; - } - - async(validator, message = `${this.fieldName} is invalid`) { - this.rules.push(new ValidationRule( - async (value) => await validator(value), - message - )); - return this; - } - - when(condition, thenValidator) { - this.rules.push(new ValidationRule( - async (value, allValues) => { - if (condition(allValues)) { - return await thenValidator(value, allValues); - } - return true; - }, - 'Conditional validation failed' - )); - return this; - } - - async validate(value, allValues = {}) { - const errors = []; - - for (const rule of this.rules) { - const result = await rule.validate(value, allValues); - if (!result.isValid) { - errors.push(result.message); - } - } - - return { - isValid: errors.length === 0, - errors - }; - } -} - -class FormValidator { - constructor() { - this.fields = {}; - } - - field(fieldName) { - if (!this.fields[fieldName]) { - this.fields[fieldName] = new FieldValidator(fieldName); - } - return this.fields[fieldName]; - } - - async validate(values) { - const results = {}; - let isValid = true; - - for (const [fieldName, validator] of Object.entries(this.fields)) { - const fieldValue = values[fieldName]; - const result = await validator.validate(fieldValue, values); - - results[fieldName] = result; - - if (!result.isValid) { - isValid = false; - } - } - - return { - isValid, - fields: results, - errors: Object.fromEntries( - Object.entries(results) - .filter(([_, result]) => !result.isValid) - .map(([field, result]) => [field, result.errors]) - ) - }; - } - - async validateField(fieldName, value, allValues = {}) { - const validator = this.fields[fieldName]; - if (!validator) { - throw new Error(`No validator found for field: ${fieldName}`); - } - - return await validator.validate(value, allValues); - } -} - -// Built-in validators -const Validators = { - required: (message) => (value) => { - return value !== undefined && value !== null && value !== ''; - }, - - email: (value) => { - if (!value) return true; - return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value); - }, - - url: (value) => { - if (!value) return true; - try { - new URL(value); - return true; - } catch { - return false; - } - }, - - minLength: (min) => (value) => { - return !value || value.length >= min; - }, - - maxLength: (max) => (value) => { - return !value || value.length <= max; - }, - - min: (min) => (value) => { - return !value || Number(value) >= min; - }, - - max: (max) => (value) => { - return !value || Number(value) <= max; - }, - - pattern: (regex) => (value) => { - return !value || regex.test(value); - }, - - oneOf: (values) => (value) => { - return !value || values.includes(value); - }, - - matches: (otherField) => (value, allValues) => { - return !value || value === allValues[otherField]; - }, - - creditCard: (value) => { - if (!value) return true; - // Luhn algorithm - const digits = value.replace(/\D/g, ''); - if (digits.length < 13 || digits.length > 19) return false; - - let sum = 0; - let isEven = false; - - for (let i = digits.length - 1; i >= 0; i--) { - let digit = parseInt(digits[i]); - - if (isEven) { - digit *= 2; - if (digit > 9) digit -= 9; - } - - sum += digit; - isEven = !isEven; - } - - return sum % 10 === 0; - }, - - date: (value) => { - if (!value) return true; - const date = new Date(value); - return !isNaN(date.getTime()); - }, - - futureDate: (value) => { - if (!value) return true; - const date = new Date(value); - return date > new Date(); - }, - - pastDate: (value) => { - if (!value) return true; - const date = new Date(value); - return date < new Date(); - }, - - age: (minAge) => (value) => { - if (!value) return true; - const birthDate = new Date(value); - const age = (new Date() - birthDate) / (365.25 * 24 * 60 * 60 * 1000); - return age >= minAge; - }, - - strongPassword: (value) => { - if (!value) return true; - return ( - value.length >= 8 && - /[a-z]/.test(value) && - /[A-Z]/.test(value) && - /\d/.test(value) && - /[!@#$%^&*]/.test(value) - ); - }, - - username: (value) => { - if (!value) return true; - return /^[a-zA-Z0-9_-]{3,20}$/.test(value); - }, - - zipCode: (country = 'US') => (value) => { - if (!value) return true; - const patterns = { - US: /^\d{5}(-\d{4})?$/, - UK: /^[A-Z]{1,2}\d{1,2}[A-Z]?\s?\d[A-Z]{2}$/i, - CA: /^[A-Z]\d[A-Z]\s?\d[A-Z]\d$/i - }; - return patterns[country]?.test(value) || false; - } -}; - -// Example usage -function demo() { - console.log('='.repeat(60)); - console.log('Form Validation Library Demo'); - console.log('='.repeat(60)); - - // Create form validator - const validator = new FormValidator(); - - // Define validation rules - validator.field('username') - .required() - .minLength(3) - .maxLength(20) - .alphanumeric('Username must contain only letters and numbers'); - - validator.field('email') - .required() - .email(); - - validator.field('password') - .required() - .minLength(8) - .custom(Validators.strongPassword, 'Password must be strong (uppercase, lowercase, number, special character)'); - - validator.field('confirmPassword') - .required() - .matches('password', 'Passwords must match'); - - validator.field('age') - .required() - .numeric() - .min(18, 'You must be at least 18 years old'); - - validator.field('website') - .url(); - - validator.field('phone') - .phone(); - - // Test data - const validData = { - username: 'john123', - email: 'john@example.com', - password: 'SecurePass123!', - confirmPassword: 'SecurePass123!', - age: '25', - website: 'https://example.com', - phone: '+1-555-123-4567' - }; - - const invalidData = { - username: 'jo', - email: 'invalid-email', - password: 'weak', - confirmPassword: 'different', - age: '15', - website: 'not-a-url', - phone: '123' - }; - - // Validate - console.log('\n--- Validating Valid Data ---'); - validator.validate(validData).then(result => { - console.log('Is Valid:', result.isValid); - if (!result.isValid) { - console.log('Errors:', JSON.stringify(result.errors, null, 2)); - } else { - console.log('All fields are valid!'); - } - }); - - console.log('\n--- Validating Invalid Data ---'); - validator.validate(invalidData).then(result => { - console.log('Is Valid:', result.isValid); - console.log('Errors:', JSON.stringify(result.errors, null, 2)); - }); -} - -// Export for Node.js -if (typeof module !== 'undefined' && module.exports) { - module.exports = { FormValidator, FieldValidator, Validators }; -} - -// Run demo if executed directly -if (typeof require !== 'undefined' && require.main === module) { - demo(); -}