enigmatic 0.34.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,15 @@
1
1
  # Enigmatic
2
2
 
3
- ![Tests](https://img.shields.io/badge/tests-26%20passing-brightgreen) ![Size](https://img.shields.io/badge/size-3.4%20KB-blue) ![Version](https://img.shields.io/npm/v/enigmatic)
3
+ ![Version](https://img.shields.io/npm/v/enigmatic)
4
4
 
5
5
  A lightweight client-side JavaScript library for DOM manipulation, reactive state management, and API interactions, with an optional Bun server for backend functionality.
6
6
 
7
+ ## Architecture
8
+
9
+ ![Client-Server Architecture](clientserver.png)
10
+
11
+ The diagram above shows the interaction between the client (browser), Bun server, and external services (Auth0 and Cloudflare R2/S3).
12
+
7
13
  ## Quick Start
8
14
 
9
15
  ### Using client.js via CDN
@@ -14,66 +20,54 @@ Include `client.js` in any HTML file using the unpkg CDN:
14
20
  <!DOCTYPE html>
15
21
  <html>
16
22
  <head>
17
- <script src="https://unpkg.com/enigmatic@0.34.0/public/client.js"></script>
23
+ <script src="https://unpkg.com/enigmatic"></script>
24
+ <script src="https://unpkg.com/enigmatic/client/public/custom.js"></script>
18
25
  <script>
19
- // Configure your API URL
20
26
  window.api_url = 'https://your-server.com';
21
-
22
- // Define custom elements
23
- window.custom = {
24
- "hello-world": (data) => `Hello ${data || 'World'}`
25
- };
27
+ window.state.message = 'Hello World';
26
28
  </script>
27
29
  </head>
28
30
  <body>
29
31
  <hello-world data="message"></hello-world>
30
- <script>
31
- window.state.message = "Hello World";
32
- </script>
33
32
  </body>
34
33
  </html>
35
34
  ```
36
35
 
37
- **Note:** Replace `0.34.0` with the latest version number from [npm](https://www.npmjs.com/package/enigmatic).
36
+ **Note:** Use `enigmatic@0.35.0` (or latest) in the URL to pin a version.
38
37
 
39
38
  ### Using the Bun Server
40
39
 
41
- The Bun server provides a complete backend implementation with:
42
- - Key-value storage (using BeeMap)
43
- - File storage (using Cloudflare R2/S3)
44
- - Authentication (using Auth0)
45
- - Static file serving
40
+ The Bun server provides a complete backend with:
41
+ - **Key-value storage** – Per-user KV persisted as append-only JSONL (`server/kv/{user}.jsonl`) with `update`/`delete` actions and timestamps
42
+ - **File storage** Per-user files via Cloudflare R2 (or S3-compatible API)
43
+ - **Authentication** Auth0 OAuth2 login/logout
44
+ - **Static files** – Served from `client/public/`
46
45
 
47
46
  #### Installation
48
47
 
49
- 1. Install Bun (if not already installed):
48
+ 1. Install [Bun](https://bun.sh):
50
49
  ```bash
51
50
  curl -fsSL https://bun.sh/install | bash
52
51
  ```
53
52
 
54
- 2. Install dependencies:
53
+ 2. Install dependencies (if any):
55
54
  ```bash
56
55
  bun install
57
56
  ```
58
57
 
59
- 3. Generate HTTPS certificates (for local development):
60
- ```bash
61
- cd server
62
- ./generate-certs.sh
63
- cd ..
64
- ```
58
+ 3. TLS certificates: place `cert.pem` and `key.pem` in `server/certs/` for HTTPS (required for Auth0 in production).
65
59
 
66
60
  #### Environment Variables
67
61
 
68
- Create a `.env` file in the project root with the following variables:
62
+ Create a `.env` file in the project root (or set env vars):
69
63
 
70
64
  ```bash
71
- # Auth0 Configuration
65
+ # Auth0
72
66
  AUTH0_DOMAIN=your-tenant.auth0.com
73
67
  AUTH0_CLIENT_ID=your-client-id
74
68
  AUTH0_CLIENT_SECRET=your-client-secret
75
69
 
76
- # Cloudflare R2 Configuration (optional, for file storage)
70
+ # Cloudflare R2 (optional, for file storage)
77
71
  CLOUDFLARE_ACCESS_KEY_ID=your-access-key-id
78
72
  CLOUDFLARE_SECRET_ACCESS_KEY=your-secret-access-key
79
73
  CLOUDFLARE_BUCKET_NAME=your-bucket-name
@@ -82,37 +76,33 @@ CLOUDFLARE_PUBLIC_URL=https://your-account-id.r2.cloudflarestorage.com
82
76
 
83
77
  #### Running the Server
84
78
 
85
- Start the server with hot reload:
86
79
  ```bash
87
80
  npm start
88
81
  # or
89
- bun --hot ./bun-server.js
82
+ npx enigmatic
83
+ # or with hot reload
84
+ npm run hot
90
85
  ```
91
86
 
92
- The server will start on `https://localhost:3000` (HTTPS is required for Auth0 cookies).
93
-
94
- #### Server Features
95
-
96
- - **Static File Serving**: Automatically serves any files from the `public/` folder
97
- - **Key-Value Storage**: Per-user KV storage using BeeMap (persisted to JSONL files)
98
- - **File Storage**: Per-user file storage using Cloudflare R2 (or compatible S3)
99
- - **Authentication**: OAuth2 flow with Auth0
100
- - **CORS**: Enabled for all origins (configurable)
87
+ Server runs at **https://localhost:3000** (HTTPS is required for Auth0 cookies).
101
88
 
102
89
  #### Server Endpoints
103
90
 
104
- - `GET /` or `GET /index.html` - Serves `public/index.html`
105
- - `GET /{path}` - Serves static files from `public/` folder
106
- - `GET /login` - Initiates Auth0 login flow
107
- - `GET /callback` - Auth0 callback handler
108
- - `GET /logout` - Logs out user
109
- - `GET /{key}` - Retrieves KV value (requires auth)
110
- - `POST /{key}` - Stores KV value (requires auth)
111
- - `DELETE /{key}` - Deletes KV value (requires auth)
112
- - `PUT /{key}` - Uploads file to R2 (requires auth)
113
- - `PURGE /{key}` - Deletes file from R2 (requires auth)
114
- - `PROPFIND /` - Lists files in R2 (requires auth)
115
- - `PATCH /{key}` - Downloads file from R2 (requires auth)
91
+ | Method | Path | Description |
92
+ |----------|------------|-------------|
93
+ | GET | `/` | Serves `client/public/index.html` |
94
+ | GET | `/index.html`, `/*.js`, etc. | Static files from `client/public/` |
95
+ | GET | `/login` | Redirects to Auth0 login |
96
+ | GET | `/callback`| Auth0 OAuth callback |
97
+ | GET | `/logout` | Logs out and clears session |
98
+ | GET | `/me` | Current user or 401 (no auth) |
99
+ | GET | `/{key}` | KV get (auth required) |
100
+ | POST | `/{key}` | KV set (auth required) |
101
+ | DELETE | `/{key}` | KV delete (auth required) |
102
+ | PUT | `/{key}` | Upload file to R2 (auth required) |
103
+ | PURGE | `/{key}` | Delete file from R2 (auth required) |
104
+ | PROPFIND | `/` | List R2 files (auth required) |
105
+ | PATCH | `/{key}` | Download file from R2 (auth required) |
116
106
 
117
107
  ## Overview
118
108
 
@@ -137,7 +127,7 @@ const elements = window.$$('.my-class');
137
127
  ### API Base URL
138
128
 
139
129
  ```javascript
140
- window.api_url = "https://localhost:3000"
130
+ window.api_url = "https://localhost:3001"
141
131
  ```
142
132
 
143
133
  Configures the base URL for all API requests. Modify this to point to your server.
@@ -283,6 +273,16 @@ window.logout();
283
273
 
284
274
  **Behavior:** Sets `window.location.href` to `{api_url}/logout`
285
275
 
276
+ #### `window.me()`
277
+ Returns the current user if authenticated, or `null` if not (e.g. 401).
278
+
279
+ ```javascript
280
+ const user = await window.me();
281
+ // user is { sub, email, ... } or null
282
+ ```
283
+
284
+ **Endpoint:** `GET {api_url}/me` (with credentials)
285
+
286
286
  ## Custom Elements System
287
287
 
288
288
  Custom elements are defined in `window.custom` object and automatically initialized when the DOM loads or when elements are added dynamically.
@@ -397,42 +397,18 @@ window.get('key').catch(err => console.error(err));
397
397
  <!DOCTYPE html>
398
398
  <html>
399
399
  <head>
400
- <script src="https://unpkg.com/enigmatic@0.34.0/public/client.js"></script>
400
+ <script src="https://unpkg.com/enigmatic"></script>
401
+ <script src="https://unpkg.com/enigmatic/client/public/custom.js"></script>
401
402
  <script>
402
- // Configure API URL
403
- window.api_url = 'https://localhost:3000';
404
-
405
- // Define custom elements
406
- window.custom = {
407
- "hello-world": (data) => `Hello ${data || 'World'}`
408
- };
403
+ window.api_url = window.api_url || window.location.origin;
404
+ window.state.message = 'World';
409
405
  </script>
410
406
  </head>
411
407
  <body>
412
- <!-- Custom element with reactive state -->
413
408
  <hello-world data="message"></hello-world>
414
-
409
+ <file-widget></file-widget>
415
410
  <script>
416
- // Set reactive state (triggers updates to elements with data="message")
417
- window.state.message = "Hello World";
418
-
419
- // Use API functions
420
- (async () => {
421
- await window.set('test', 'value');
422
- const value = await window.get('test');
423
- console.log(value);
424
-
425
- // Upload file
426
- const fileInput = document.querySelector('input[type="file"]');
427
- fileInput.onchange = async (e) => {
428
- const file = e.target.files[0];
429
- await window.put(file.name, file);
430
- };
431
-
432
- // List files
433
- const files = await window.list();
434
- console.log(files);
435
- })();
411
+ window.me().then(u => console.log(u ? 'Logged in as ' + u.email : 'Not logged in'));
436
412
  </script>
437
413
  </body>
438
414
  </html>
@@ -448,7 +424,7 @@ window.get('key').catch(err => console.error(err));
448
424
  - `URL.createObjectURL`
449
425
  - `MutationObserver` API
450
426
 
451
- **Note:** Custom element definitions can be loaded before or after `client.js` - the Proxy system will handle initialization either way.
427
+ **Note:** Load `client.js` first, then your custom element definitions (e.g. `custom.js`); the Proxy initializes elements when definitions are assigned.
452
428
 
453
429
  ## Notes
454
430
 
@@ -464,19 +440,9 @@ window.get('key').catch(err => console.error(err));
464
440
 
465
441
  ## Development
466
442
 
467
- ### Running Tests
468
-
469
- ```bash
470
- npm test
471
- ```
472
-
473
- ### Building
474
-
475
- The library is ready to use as-is. For production, you can use the minified version:
476
-
477
- ```html
478
- <script src="https://unpkg.com/enigmatic@0.34.0/public/client.min.js"></script>
479
- ```
443
+ - **Start server:** `npm start` or `npx enigmatic`
444
+ - **Hot reload:** `npm run hot`
445
+ - **Client:** Load `client.js` from unpkg or from `client/public/client.js` when serving locally. Load `custom.js` (or your definitions) after `client.js`; set `window.api_url` before making API calls.
480
446
 
481
447
  ## License
482
448
 
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bun
2
+ import config from "../server/bun-server.js";
3
+ Bun.serve(config);
@@ -0,0 +1,314 @@
1
+ # Custom Web Components Instructions for LLMs
2
+
3
+ ## Overview
4
+
5
+ When users request web components to be created, you MUST use the `window.custom` system. You can see examples at https://unpkg.com/enigmatic/client/public/custom.js or in the repo at `client/public/custom.js`. This system allows you to define custom HTML elements that automatically render and can be reactive to state changes.
6
+
7
+ ## System Architecture
8
+
9
+ The custom component system works by:
10
+ 1. Defining components in `window.custom` object
11
+ 2. Using custom HTML tags in the DOM (e.g., `<my-component></my-component>`)
12
+ 3. Components automatically initialize when the DOM loads or when elements are added
13
+ 4. Components can be reactive to `window.state` changes via `data` attributes
14
+
15
+ ## Component Definition Format
16
+
17
+ ### Format 1: Function-Based Component (Simple)
18
+
19
+ **Use this format for simple components that just render HTML:**
20
+
21
+ ```javascript
22
+ window.custom = {
23
+ "mycomponent": (data) => {
24
+ // data is the value from window.state if element has data="key" attribute
25
+ // If no data attribute, data will be undefined
26
+ return `<div>Your HTML here: ${data || 'default'}</div>`;
27
+ }
28
+ };
29
+ ```
30
+
31
+ **Example from custom.js:**
32
+ ```javascript
33
+ window.custom = {
34
+ "hello-world": (data) => `Hello ${data}`,
35
+ };
36
+ ```
37
+
38
+ **HTML Usage:**
39
+ ```html
40
+ <hello-world></hello-world>
41
+ <!-- or with reactive state -->
42
+ <hello-world data="message"></hello-world>
43
+ <script>
44
+ window.state.message = "World"; // Component updates automatically
45
+ </script>
46
+ ```
47
+
48
+ ### Format 2: Object-Based Component (Advanced)
49
+
50
+ **Use this format when you need methods, properties, or more complex logic:**
51
+
52
+ ```javascript
53
+ window.custom = {
54
+ "component-name": {
55
+ // Optional helper methods
56
+ prop: (data) => `Processed: ${data}`,
57
+
58
+ // Required: render method that returns HTML string
59
+ render: function(data) {
60
+ // Use 'this' to access other methods/properties
61
+ return `<div>${this.prop(data)}</div>`;
62
+ }
63
+ }
64
+ };
65
+ ```
66
+
67
+ **Example from custom.js:**
68
+ ```javascript
69
+ window.custom = {
70
+ "hello-world-2": {
71
+ prop: (data) => `${data} World`,
72
+ render: function(data) {
73
+ return this.prop(data);
74
+ }
75
+ }
76
+ };
77
+ ```
78
+
79
+ **HTML Usage:**
80
+ ```html
81
+ <hello-world-2 data="greeting"></hello-world-2>
82
+ <script>
83
+ window.state.greeting = "Hello"; // Component updates automatically
84
+ </script>
85
+ ```
86
+
87
+ ### Format 3: Async Component
88
+
89
+ **Use this format when you need to fetch data or perform async operations:**
90
+
91
+ ```javascript
92
+ window.custom = {
93
+ "async-component": async () => {
94
+ try {
95
+ const data = await window.get('some-key');
96
+ // Or use other API functions: window.list(), window.set(), etc.
97
+ return `<div>${JSON.stringify(data)}</div>`;
98
+ } catch (err) {
99
+ return `<div>Error: ${err.message}</div>`;
100
+ }
101
+ }
102
+ };
103
+ ```
104
+
105
+ **Example from custom.js:**
106
+ ```javascript
107
+ window.custom = {
108
+ "file-widget": async () => {
109
+ try {
110
+ const list = await window.list();
111
+ // Render file list with styles and interactive elements
112
+ return `<div>...</div>`;
113
+ } catch (err) {
114
+ return `<div>Error occurred</div>`;
115
+ }
116
+ }
117
+ };
118
+ ```
119
+
120
+ ## Key Rules and Patterns
121
+
122
+ ### 1. Component Names
123
+ - Use kebab-case (e.g., `"my-component"`, `"file-widget"`)
124
+ - Must match the HTML tag name exactly (case-insensitive)
125
+ - Example: `window.custom["my-component"]` → `<my-component></my-component>`
126
+
127
+ ### 2. Return Value
128
+ - Components MUST return a string containing HTML
129
+ - Can include inline `<style>` tags for component-specific styles
130
+ - Can include inline event handlers using `onclick`, `onchange`, etc.
131
+
132
+ ### 3. Reactive State Integration
133
+ - Components automatically receive state values when element has `data="key"` attribute
134
+ - When `window.state.key = value` is set, all elements with `data="key"` are re-rendered
135
+ - Access state value as the first parameter: `(data) => ...` or `render: function(data) { ... }`
136
+
137
+ ### 4. Async Operations
138
+ - Use `async` functions when you need to fetch data
139
+ - Use `window.get()`, `window.set()`, `window.list()`, `window.put()`, `window.delete()`, `window.purge()`, `window.me()`, `window.download()`, etc. for API calls (all use `window.api_url`)
140
+ - Always wrap async operations in try-catch for error handling
141
+
142
+ ### 5. Styling
143
+ - Include styles inline using `<style>` tags within the returned HTML
144
+ - Use scoped class names (e.g., `.w-c`, `.w-i`) to avoid conflicts
145
+ - Keep styles minimal and component-specific
146
+
147
+ ### 6. Event Handlers
148
+ - Use inline event handlers: `onclick="..."`, `onchange="..."`
149
+ - Can call `window` API functions directly: `onclick="window.download('file.txt')"`
150
+ - For async operations, wrap in IIFE: `onclick="(async()=>{await window.purge('file');location.reload()})()"`
151
+
152
+ ## Complete Examples
153
+
154
+ ### Example 1: Simple Counter Component
155
+
156
+ ```javascript
157
+ window.custom = {
158
+ "counter": (data) => {
159
+ const count = data || 0;
160
+ return `
161
+ <div style="padding: 20px; text-align: center;">
162
+ <h2>Count: ${count}</h2>
163
+ <button onclick="window.state.count = (window.state.count || 0) + 1">+</button>
164
+ <button onclick="window.state.count = (window.state.count || 0) - 1">-</button>
165
+ </div>
166
+ `;
167
+ }
168
+ };
169
+ ```
170
+
171
+ **HTML:**
172
+ ```html
173
+ <counter data="count"></counter>
174
+ <script>
175
+ window.state.count = 0;
176
+ </script>
177
+ ```
178
+
179
+ ### Example 2: Data Display Component
180
+
181
+ ```javascript
182
+ window.custom = {
183
+ "data-display": async () => {
184
+ try {
185
+ const data = await window.get('user-data');
186
+ return `
187
+ <div style="padding: 15px; border: 1px solid #ddd; border-radius: 5px;">
188
+ <h3>User Data</h3>
189
+ <pre>${JSON.stringify(data, null, 2)}</pre>
190
+ </div>
191
+ `;
192
+ } catch (err) {
193
+ return `<div style="color: red;">Error loading data: ${err.message}</div>`;
194
+ }
195
+ }
196
+ };
197
+ ```
198
+
199
+ **HTML:**
200
+ ```html
201
+ <data-display></data-display>
202
+ ```
203
+
204
+ ### Example 3: Form Component with Object Methods
205
+
206
+ ```javascript
207
+ window.custom = {
208
+ "contact-form": {
209
+ validate: (email) => email && email.includes('@'),
210
+
211
+ render: function(data) {
212
+ const email = data?.email || '';
213
+ const isValid = this.validate(email);
214
+
215
+ return `
216
+ <style>
217
+ .form-container { padding: 20px; max-width: 400px; }
218
+ .form-input { width: 100%; padding: 8px; margin: 5px 0; }
219
+ .form-submit { background: #007bff; color: white; padding: 10px 20px; border: none; cursor: pointer; }
220
+ </style>
221
+ <div class="form-container">
222
+ <input type="email" class="form-input"
223
+ placeholder="Email"
224
+ value="${email}"
225
+ onchange="window.state.formData = {...(window.state.formData || {}), email: this.value}">
226
+ <button class="form-submit"
227
+ onclick="alert('Form submitted!')"
228
+ ${!isValid ? 'disabled' : ''}>
229
+ Submit
230
+ </button>
231
+ </div>
232
+ `;
233
+ }
234
+ }
235
+ };
236
+ ```
237
+
238
+ **HTML:**
239
+ ```html
240
+ <contact-form data="formData"></contact-form>
241
+ <script>
242
+ window.state.formData = { email: '' };
243
+ </script>
244
+ ```
245
+
246
+ ## When to Use Each Format
247
+
248
+ - **Function format**: Simple components, static content, or when you only need the data parameter
249
+ - **Object format**: When you need helper methods, want to organize code better, or need `this` context
250
+ - **Async format**: When you need to fetch data from APIs, perform async operations, or load external resources
251
+
252
+ ## Important Notes
253
+
254
+ 1. **Always return HTML strings** - Components must return valid HTML strings
255
+ 2. **Handle errors** - Wrap async operations in try-catch blocks
256
+ 3. **Use kebab-case** - Component names must be in kebab-case to match HTML tag names
257
+ 4. **State reactivity** - Use `data="key"` attribute to make components reactive to `window.state[key]`
258
+ 5. **Auto-initialization** - Components automatically initialize when:
259
+ - DOM loads
260
+ - Component is added to `window.custom`
261
+ - New matching elements are added to the DOM
262
+
263
+ ## Common Patterns
264
+
265
+ ### Pattern: Loading State
266
+ ```javascript
267
+ window.custom = {
268
+ "loading-component": async () => {
269
+ try {
270
+ const data = await window.get('data');
271
+ return data ? `<div>${data}</div>` : '<div>No data</div>';
272
+ } catch (err) {
273
+ return `<div>Error: ${err.message}</div>`;
274
+ }
275
+ }
276
+ };
277
+ ```
278
+
279
+ ### Pattern: Error Handling
280
+ ```javascript
281
+ window.custom = {
282
+ "safe-component": async () => {
283
+ try {
284
+ const result = await window.list();
285
+ return `<div>Success: ${result.length} items</div>`;
286
+ } catch (err) {
287
+ return `<div style="color: red;">Error: ${err.message}</div>`;
288
+ }
289
+ }
290
+ };
291
+ ```
292
+
293
+ ### Pattern: Conditional Rendering
294
+ ```javascript
295
+ window.custom = {
296
+ "conditional": (data) => {
297
+ if (!data) return '<div>No data</div>';
298
+ if (data.error) return `<div>Error: ${data.error}</div>`;
299
+ return `<div>Data: ${data.value}</div>`;
300
+ }
301
+ };
302
+ ```
303
+
304
+ ## Summary
305
+
306
+ When creating web components:
307
+ 1. Define them in `window.custom` object
308
+ 2. Use kebab-case names matching HTML tag names
309
+ 3. Return HTML strings (can include styles and event handlers)
310
+ 4. Use function format for simple components
311
+ 5. Use object format with `render` method for complex components
312
+ 6. Use async functions when fetching data
313
+ 7. Make components reactive by using `data="key"` attributes
314
+ 8. Always handle errors in async components