enigmatic 0.26.0 → 0.29.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/CLIENT_JS_DOCS.md +322 -0
- package/__tests__/e2.test.js +231 -138
- package/__tests__/jest.config.js +7 -0
- package/{jest.setup.js → __tests__/jest.setup.js} +4 -0
- package/bun-server.js +130 -0
- package/package.json +12 -11
- package/public/client.js +98 -0
- package/public/custom.js +29 -0
- package/public/index.html +45 -0
- package/README.md +0 -218
- package/__tests__/enigmatic.test.js +0 -328
- package/components.js +0 -169
- package/e2.js +0 -38
- package/enigmatic.js +0 -248
- package/index.html +0 -34
- package/jest.config.js +0 -6
- /package/{enigmatic.css → public/client.css} +0 -0
- /package/{theme.css → public/theme.css} +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# client.js Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`client.js` is a client-side JavaScript library that provides utilities for DOM manipulation, reactive state management, and API interactions with a backend server. It automatically initializes custom HTML elements and provides a simple API for key-value storage, file operations, and authentication.
|
|
6
|
+
|
|
7
|
+
## Core Utilities
|
|
8
|
+
|
|
9
|
+
### DOM Selectors
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
window.$ // Alias for document.querySelector
|
|
13
|
+
window.$$ // Alias for document.querySelectorAll
|
|
14
|
+
window.$c // Alias for element.closest (requires $0 context)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Usage:**
|
|
18
|
+
```javascript
|
|
19
|
+
const element = window.$('#my-id');
|
|
20
|
+
const elements = window.$$('.my-class');
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### API Base URL
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
window.api_url = "https://localhost:3000"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Configures the base URL for all API requests. Modify this to point to your server.
|
|
30
|
+
|
|
31
|
+
## Reactive State Management
|
|
32
|
+
|
|
33
|
+
`window.state` is a Proxy object that automatically updates DOM elements when properties change.
|
|
34
|
+
|
|
35
|
+
**How it works:**
|
|
36
|
+
- Set a property: `window.state.myKey = 'value'`
|
|
37
|
+
- Elements with `data="myKey"` attribute are automatically updated
|
|
38
|
+
- The system looks for custom element handlers in `window.custom[tagName]`
|
|
39
|
+
- Supports both function and object-based custom elements
|
|
40
|
+
|
|
41
|
+
**Example:**
|
|
42
|
+
```html
|
|
43
|
+
<div data="message">Initial</div>
|
|
44
|
+
<script>
|
|
45
|
+
window.state.message = "Updated!"; // Automatically updates the div
|
|
46
|
+
</script>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Custom Element Integration:**
|
|
50
|
+
- If `window.custom[tagName]` is a function: calls `f(value)` and sets `innerHTML`
|
|
51
|
+
- If `window.custom[tagName]` is an object: calls `f.render(value)` and sets `innerHTML`
|
|
52
|
+
|
|
53
|
+
## API Functions
|
|
54
|
+
|
|
55
|
+
All API functions are async and return Promises. They use `window.api_url` as the base URL.
|
|
56
|
+
|
|
57
|
+
### KV Storage Operations
|
|
58
|
+
|
|
59
|
+
#### `window.get(key)`
|
|
60
|
+
Retrieves a value from the server's key-value store.
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
const value = await window.get('my-key');
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**HTTP Method:** GET
|
|
67
|
+
**Endpoint:** `{api_url}/{key}`
|
|
68
|
+
**Returns:** Parsed JSON response
|
|
69
|
+
|
|
70
|
+
#### `window.set(key, value)`
|
|
71
|
+
Stores a value in the server's key-value store.
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
await window.set('my-key', 'my-value');
|
|
75
|
+
await window.set('my-key', { json: 'object' });
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**HTTP Method:** POST
|
|
79
|
+
**Endpoint:** `{api_url}/{key}`
|
|
80
|
+
**Body:** String values sent as-is, objects are JSON stringified
|
|
81
|
+
**Returns:** Parsed JSON response
|
|
82
|
+
|
|
83
|
+
#### `window.delete(key)`
|
|
84
|
+
Deletes a key from the server's key-value store.
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
await window.delete('my-key');
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**HTTP Method:** DELETE
|
|
91
|
+
**Endpoint:** `{api_url}/{key}`
|
|
92
|
+
**Returns:** Parsed JSON response
|
|
93
|
+
|
|
94
|
+
### R2 Storage Operations (File Storage)
|
|
95
|
+
|
|
96
|
+
#### `window.put(key, body)`
|
|
97
|
+
Uploads a file or data to R2 storage.
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
await window.put('filename.txt', 'file content');
|
|
101
|
+
await window.put('image.png', blob);
|
|
102
|
+
await window.put('data.json', { json: 'data' });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**HTTP Method:** PUT
|
|
106
|
+
**Endpoint:** `{api_url}/{key}`
|
|
107
|
+
**Body:** Accepts Blob, string, or JSON-serializable objects
|
|
108
|
+
**Returns:** Parsed JSON response
|
|
109
|
+
|
|
110
|
+
#### `window.purge(key)`
|
|
111
|
+
Deletes a file from R2 storage.
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
await window.purge('filename.txt');
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**HTTP Method:** PURGE
|
|
118
|
+
**Endpoint:** `{api_url}/{key}`
|
|
119
|
+
**Returns:** Parsed JSON response
|
|
120
|
+
|
|
121
|
+
#### `window.list()`
|
|
122
|
+
Lists all files in the current user's R2 storage.
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
const files = await window.list();
|
|
126
|
+
// Returns: [{ name: 'file1.txt', lastModified: '...', size: 123 }, ...]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**HTTP Method:** PROPFIND
|
|
130
|
+
**Endpoint:** `{api_url}/` (base URL, no key)
|
|
131
|
+
**Returns:** Array of file objects with `name`, `lastModified`, and `size` properties
|
|
132
|
+
|
|
133
|
+
#### `window.download(key)`
|
|
134
|
+
Downloads a file from R2 storage and triggers browser download.
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
await window.download('filename.txt');
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**HTTP Method:** PATCH
|
|
141
|
+
**Endpoint:** `{api_url}/{key}`
|
|
142
|
+
**Behavior:**
|
|
143
|
+
- Fetches file as blob
|
|
144
|
+
- Creates temporary download URL
|
|
145
|
+
- Triggers browser download
|
|
146
|
+
- Cleans up temporary URL
|
|
147
|
+
|
|
148
|
+
**Note:** Uses PATCH method due to browser limitations with custom HTTP methods.
|
|
149
|
+
|
|
150
|
+
### Authentication
|
|
151
|
+
|
|
152
|
+
#### `window.login()`
|
|
153
|
+
Redirects to the server's login endpoint.
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
window.login();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Behavior:** Sets `window.location.href` to `{api_url}/login`
|
|
160
|
+
|
|
161
|
+
#### `window.logout()`
|
|
162
|
+
Redirects to the server's logout endpoint.
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
window.logout();
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Behavior:** Sets `window.location.href` to `{api_url}/logout`
|
|
169
|
+
|
|
170
|
+
## Custom Elements System
|
|
171
|
+
|
|
172
|
+
Custom elements are defined in `window.custom` object and automatically initialized when the DOM loads.
|
|
173
|
+
|
|
174
|
+
### Initialization
|
|
175
|
+
|
|
176
|
+
The library automatically:
|
|
177
|
+
1. Waits for DOM to be ready (`DOMContentLoaded` or immediate if already loaded)
|
|
178
|
+
2. Iterates through all keys in `window.custom`
|
|
179
|
+
3. Finds all matching HTML elements by tag name
|
|
180
|
+
4. Calls the custom element handler and sets `innerHTML`
|
|
181
|
+
|
|
182
|
+
### Defining Custom Elements
|
|
183
|
+
|
|
184
|
+
#### Function-based Custom Element
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
window.custom = {
|
|
188
|
+
"my-element": async (data) => {
|
|
189
|
+
return `<div>Content: ${data}</div>`;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**HTML Usage:**
|
|
195
|
+
```html
|
|
196
|
+
<my-element></my-element>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
When `window.state.myKey = 'value'` is set and an element has `data="myKey"`:
|
|
200
|
+
```html
|
|
201
|
+
<my-element data="myKey"></my-element>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The function receives the state value as the first parameter.
|
|
205
|
+
|
|
206
|
+
#### Object-based Custom Element
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
window.custom = {
|
|
210
|
+
"my-element": {
|
|
211
|
+
prop: (data) => `Processed: ${data}`,
|
|
212
|
+
render: function(data) {
|
|
213
|
+
return `<div>${this.prop(data)}</div>`;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**HTML Usage:**
|
|
220
|
+
```html
|
|
221
|
+
<my-element></my-element>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
When used with reactive state, the `render` method is called with the state value.
|
|
225
|
+
|
|
226
|
+
### Example: File Widget
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
window.custom = {
|
|
230
|
+
"file-widget": async () => {
|
|
231
|
+
const list = await window.list();
|
|
232
|
+
// Returns HTML string with file list and upload button
|
|
233
|
+
return `<div>...</div>`;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**HTML Usage:**
|
|
239
|
+
```html
|
|
240
|
+
<file-widget></file-widget>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
This custom element:
|
|
244
|
+
- Fetches file list using `window.list()`
|
|
245
|
+
- Renders file items with download and delete buttons
|
|
246
|
+
- Includes an upload button
|
|
247
|
+
- Uses inline event handlers that call `window.download()`, `window.purge()`, and `window.put()`
|
|
248
|
+
|
|
249
|
+
## Error Handling
|
|
250
|
+
|
|
251
|
+
All API functions throw errors if the request fails. Use try-catch or `.catch()`:
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
try {
|
|
255
|
+
await window.get('nonexistent');
|
|
256
|
+
} catch (err) {
|
|
257
|
+
console.error('Error:', err);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Or with promises
|
|
261
|
+
window.get('key').catch(err => console.error(err));
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Complete Example
|
|
265
|
+
|
|
266
|
+
```html
|
|
267
|
+
<!DOCTYPE html>
|
|
268
|
+
<html>
|
|
269
|
+
<head>
|
|
270
|
+
<script src="custom.js"></script>
|
|
271
|
+
<script src="client.js"></script>
|
|
272
|
+
</head>
|
|
273
|
+
<body>
|
|
274
|
+
<!-- Custom element -->
|
|
275
|
+
<file-widget></file-widget>
|
|
276
|
+
|
|
277
|
+
<!-- Reactive state element -->
|
|
278
|
+
<div data="message">Initial</div>
|
|
279
|
+
|
|
280
|
+
<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
|
+
})();
|
|
301
|
+
</script>
|
|
302
|
+
</body>
|
|
303
|
+
</html>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Dependencies
|
|
307
|
+
|
|
308
|
+
- Requires `custom.js` to be loaded before `client.js` if using custom elements
|
|
309
|
+
- Requires a backend server that implements the API endpoints
|
|
310
|
+
- Requires browser support for:
|
|
311
|
+
- `fetch` API
|
|
312
|
+
- `Proxy` API
|
|
313
|
+
- `Blob` API
|
|
314
|
+
- `URL.createObjectURL`
|
|
315
|
+
|
|
316
|
+
## Notes
|
|
317
|
+
|
|
318
|
+
- All API functions automatically encode keys using `encodeURIComponent`
|
|
319
|
+
- 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
|
|
321
|
+
- The reactive state system only updates elements with matching `data` attributes
|
|
322
|
+
- Custom element handlers can be async functions
|