enigmatic 0.33.0 → 0.35.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.
@@ -1,4 +1,108 @@
1
- # client.js Documentation
1
+ # Enigmatic
2
+
3
+ ![Version](https://img.shields.io/npm/v/enigmatic)
4
+
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
+
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
+
13
+ ## Quick Start
14
+
15
+ ### Using client.js via CDN
16
+
17
+ Include `client.js` in any HTML file using the unpkg CDN:
18
+
19
+ ```html
20
+ <!DOCTYPE html>
21
+ <html>
22
+ <head>
23
+ <script src="https://unpkg.com/enigmatic"></script>
24
+ <script src="https://unpkg.com/enigmatic/client/public/custom.js"></script>
25
+ <script>
26
+ window.api_url = 'https://your-server.com';
27
+ window.state.message = 'Hello World';
28
+ </script>
29
+ </head>
30
+ <body>
31
+ <hello-world data="message"></hello-world>
32
+ </body>
33
+ </html>
34
+ ```
35
+
36
+ **Note:** Use `enigmatic@0.35.0` (or latest) in the URL to pin a version.
37
+
38
+ ### Using the Bun Server
39
+
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/`
45
+
46
+ #### Installation
47
+
48
+ 1. Install [Bun](https://bun.sh):
49
+ ```bash
50
+ curl -fsSL https://bun.sh/install | bash
51
+ ```
52
+
53
+ 2. Install dependencies (if any):
54
+ ```bash
55
+ bun install
56
+ ```
57
+
58
+ 3. TLS certificates: place `cert.pem` and `key.pem` in `server/certs/` for HTTPS (required for Auth0 in production).
59
+
60
+ #### Environment Variables
61
+
62
+ Create a `.env` file in the project root (or set env vars):
63
+
64
+ ```bash
65
+ # Auth0
66
+ AUTH0_DOMAIN=your-tenant.auth0.com
67
+ AUTH0_CLIENT_ID=your-client-id
68
+ AUTH0_CLIENT_SECRET=your-client-secret
69
+
70
+ # Cloudflare R2 (optional, for file storage)
71
+ CLOUDFLARE_ACCESS_KEY_ID=your-access-key-id
72
+ CLOUDFLARE_SECRET_ACCESS_KEY=your-secret-access-key
73
+ CLOUDFLARE_BUCKET_NAME=your-bucket-name
74
+ CLOUDFLARE_PUBLIC_URL=https://your-account-id.r2.cloudflarestorage.com
75
+ ```
76
+
77
+ #### Running the Server
78
+
79
+ ```bash
80
+ npm start
81
+ # or
82
+ npx enigmatic
83
+ # or with hot reload
84
+ npm run hot
85
+ ```
86
+
87
+ Server runs at **https://localhost:3000** (HTTPS is required for Auth0 cookies).
88
+
89
+ #### Server Endpoints
90
+
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) |
2
106
 
3
107
  ## Overview
4
108
 
@@ -36,13 +140,15 @@ Configures the base URL for all API requests. Modify this to point to your serve
36
140
  - Set a property: `window.state.myKey = 'value'`
37
141
  - Elements with `data="myKey"` attribute are automatically updated
38
142
  - The system looks for custom element handlers in `window.custom[tagName]`
143
+ - Only elements with matching custom element handlers are updated
39
144
  - Supports both function and object-based custom elements
40
145
 
41
146
  **Example:**
42
147
  ```html
43
- <div data="message">Initial</div>
148
+ <my-element data="message">Initial</my-element>
44
149
  <script>
45
- window.state.message = "Updated!"; // Automatically updates the div
150
+ window.custom['my-element'] = (data) => `<div>${data}</div>`;
151
+ window.state.message = "Updated!"; // Automatically updates the element
46
152
  </script>
47
153
  ```
48
154
 
@@ -167,9 +273,19 @@ window.logout();
167
273
 
168
274
  **Behavior:** Sets `window.location.href` to `{api_url}/logout`
169
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
+
170
286
  ## Custom Elements System
171
287
 
172
- Custom elements are defined in `window.custom` object and automatically initialized when the DOM loads.
288
+ Custom elements are defined in `window.custom` object and automatically initialized when the DOM loads or when elements are added dynamically.
173
289
 
174
290
  ### Initialization
175
291
 
@@ -178,6 +294,17 @@ The library automatically:
178
294
  2. Iterates through all keys in `window.custom`
179
295
  3. Finds all matching HTML elements by tag name
180
296
  4. Calls the custom element handler and sets `innerHTML`
297
+ 5. Watches for new elements added to the DOM via MutationObserver and initializes them automatically
298
+
299
+ ### Proxy Behavior
300
+
301
+ `window.custom` is a Proxy that automatically initializes matching elements when you add a new custom element definition:
302
+
303
+ ```javascript
304
+ // Adding a new custom element automatically initializes all matching elements in the DOM
305
+ window.custom['my-element'] = (data) => `<div>${data}</div>`;
306
+ // All <my-element> tags are immediately initialized
307
+ ```
181
308
 
182
309
  ### Defining Custom Elements
183
310
 
@@ -196,12 +323,15 @@ window.custom = {
196
323
  <my-element></my-element>
197
324
  ```
198
325
 
199
- When `window.state.myKey = 'value'` is set and an element has `data="myKey"`:
326
+ When used with reactive state, the function receives the state value:
200
327
  ```html
201
328
  <my-element data="myKey"></my-element>
329
+ <script>
330
+ window.state.myKey = 'value'; // Function is called with 'value'
331
+ </script>
202
332
  ```
203
333
 
204
- The function receives the state value as the first parameter.
334
+ The function receives the state value as the first parameter. If no state value is set, it receives `undefined`.
205
335
 
206
336
  #### Object-based Custom Element
207
337
 
@@ -267,37 +397,18 @@ window.get('key').catch(err => console.error(err));
267
397
  <!DOCTYPE html>
268
398
  <html>
269
399
  <head>
270
- <script src="custom.js"></script>
271
- <script src="client.js"></script>
400
+ <script src="https://unpkg.com/enigmatic"></script>
401
+ <script src="https://unpkg.com/enigmatic/client/public/custom.js"></script>
402
+ <script>
403
+ window.api_url = window.api_url || window.location.origin;
404
+ window.state.message = 'World';
405
+ </script>
272
406
  </head>
273
407
  <body>
274
- <!-- Custom element -->
408
+ <hello-world data="message"></hello-world>
275
409
  <file-widget></file-widget>
276
-
277
- <!-- Reactive state element -->
278
- <div data="message">Initial</div>
279
-
280
410
  <script>
281
- // Set reactive state
282
- window.state.message = "Hello World";
283
-
284
- // Use API functions
285
- (async () => {
286
- await window.set('test', 'value');
287
- const value = await window.get('test');
288
- console.log(value);
289
-
290
- // Upload file
291
- const fileInput = document.querySelector('input[type="file"]');
292
- fileInput.onchange = async (e) => {
293
- const file = e.target.files[0];
294
- await window.put(file.name, file);
295
- };
296
-
297
- // List files
298
- const files = await window.list();
299
- console.log(files);
300
- })();
411
+ window.me().then(u => console.log(u ? 'Logged in as ' + u.email : 'Not logged in'));
301
412
  </script>
302
413
  </body>
303
414
  </html>
@@ -305,18 +416,34 @@ window.get('key').catch(err => console.error(err));
305
416
 
306
417
  ## Dependencies
307
418
 
308
- - Requires `custom.js` to be loaded before `client.js` if using custom elements
309
- - Requires a backend server that implements the API endpoints
419
+ - Requires a backend server that implements the API endpoints (or use the included Bun server)
310
420
  - Requires browser support for:
311
421
  - `fetch` API
312
422
  - `Proxy` API
313
423
  - `Blob` API
314
424
  - `URL.createObjectURL`
425
+ - `MutationObserver` API
426
+
427
+ **Note:** Load `client.js` first, then your custom element definitions (e.g. `custom.js`); the Proxy initializes elements when definitions are assigned.
315
428
 
316
429
  ## Notes
317
430
 
318
431
  - All API functions automatically encode keys using `encodeURIComponent`
319
432
  - The `window.download()` function uses PATCH method internally (browsers don't support custom HTTP methods)
320
- - Custom elements are initialized once on page load; use `location.reload()` to refresh
433
+ - Custom elements are automatically initialized:
434
+ - On page load (when DOM is ready)
435
+ - When new custom element definitions are added to `window.custom`
436
+ - When new matching elements are added to the DOM (via MutationObserver)
321
437
  - The reactive state system only updates elements with matching `data` attributes
322
438
  - Custom element handlers can be async functions
439
+ - When a custom element has a `data` attribute, it automatically reads from `window.state[dataValue]` if no explicit value is provided
440
+
441
+ ## Development
442
+
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.
446
+
447
+ ## License
448
+
449
+ MIT
@@ -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