@kaiserofthenight/human-js 1.0.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.
@@ -0,0 +1,167 @@
1
+ /**
2
+ * HTTP CLIENT PLUGIN
3
+ *
4
+ * Simple fetch wrapper with better ergonomics.
5
+ * Inspired by Axios but much simpler.
6
+ */
7
+
8
+ /**
9
+ * Create an HTTP client
10
+ * @param {Object} config - Default configuration
11
+ */
12
+ export function createHttp(config = {}) {
13
+ const {
14
+ baseURL = '',
15
+ headers = {},
16
+ timeout = 30000,
17
+ onRequest = null,
18
+ onResponse = null,
19
+ onError = null
20
+ } = config;
21
+
22
+ /**
23
+ * Make HTTP request
24
+ */
25
+ async function request(url, options = {}) {
26
+ // Merge headers
27
+ const requestHeaders = {
28
+ 'Content-Type': 'application/json',
29
+ ...headers,
30
+ ...options.headers
31
+ };
32
+
33
+ // Build full URL
34
+ const fullUrl = url.startsWith('http') ? url : `${baseURL}${url}`;
35
+
36
+ // Request config
37
+ const fetchOptions = {
38
+ method: options.method || 'GET',
39
+ headers: requestHeaders,
40
+ ...options
41
+ };
42
+
43
+ // Add body for non-GET requests
44
+ if (options.body && typeof options.body === 'object') {
45
+ fetchOptions.body = JSON.stringify(options.body);
46
+ }
47
+
48
+ // Call onRequest interceptor
49
+ if (onRequest) {
50
+ const modified = onRequest(fetchOptions);
51
+ if (modified) Object.assign(fetchOptions, modified);
52
+ }
53
+
54
+ try {
55
+ // Create abort controller for timeout
56
+ const controller = new AbortController();
57
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
58
+ fetchOptions.signal = controller.signal;
59
+
60
+ // Make request
61
+ const response = await fetch(fullUrl, fetchOptions);
62
+ clearTimeout(timeoutId);
63
+
64
+ // Parse response
65
+ let data;
66
+ const contentType = response.headers.get('content-type');
67
+
68
+ if (contentType && contentType.includes('application/json')) {
69
+ data = await response.json();
70
+ } else {
71
+ data = await response.text();
72
+ }
73
+
74
+ // Build response object
75
+ const result = {
76
+ data,
77
+ status: response.status,
78
+ statusText: response.statusText,
79
+ headers: response.headers,
80
+ ok: response.ok
81
+ };
82
+
83
+ // Call onResponse interceptor
84
+ if (onResponse) {
85
+ const modified = onResponse(result);
86
+ if (modified) return modified;
87
+ }
88
+
89
+ // Throw on error status
90
+ if (!response.ok) {
91
+ throw new HttpError(result.statusText, result);
92
+ }
93
+
94
+ return result;
95
+ } catch (error) {
96
+ // Call onError interceptor
97
+ if (onError) {
98
+ onError(error);
99
+ }
100
+
101
+ throw error;
102
+ }
103
+ }
104
+
105
+ // Convenience methods
106
+ return {
107
+ request,
108
+
109
+ get(url, options = {}) {
110
+ return request(url, { ...options, method: 'GET' });
111
+ },
112
+
113
+ post(url, body, options = {}) {
114
+ return request(url, { ...options, method: 'POST', body });
115
+ },
116
+
117
+ put(url, body, options = {}) {
118
+ return request(url, { ...options, method: 'PUT', body });
119
+ },
120
+
121
+ patch(url, body, options = {}) {
122
+ return request(url, { ...options, method: 'PATCH', body });
123
+ },
124
+
125
+ delete(url, options = {}) {
126
+ return request(url, { ...options, method: 'DELETE' });
127
+ }
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Custom HTTP error
133
+ */
134
+ class HttpError extends Error {
135
+ constructor(message, response) {
136
+ super(message);
137
+ this.name = 'HttpError';
138
+ this.response = response;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Default HTTP client instance
144
+ */
145
+ export const http = createHttp();
146
+
147
+ /**
148
+ * Example usage:
149
+ *
150
+ * // Simple request
151
+ * const { data } = await http.get('/api/users');
152
+ *
153
+ * // With custom client
154
+ * const api = createHttp({
155
+ * baseURL: 'https://api.example.com',
156
+ * headers: { 'Authorization': 'Bearer token' },
157
+ * onRequest: (config) => {
158
+ * console.log('Making request:', config);
159
+ * },
160
+ * onError: (error) => {
161
+ * console.error('Request failed:', error);
162
+ * }
163
+ * });
164
+ *
165
+ * const users = await api.get('/users');
166
+ * await api.post('/users', { name: 'John' });
167
+ */
@@ -0,0 +1,181 @@
1
+ /**
2
+ * STORAGE PLUGIN
3
+ *
4
+ * Simple wrapper around localStorage and sessionStorage.
5
+ * Automatically handles JSON serialization.
6
+ */
7
+
8
+ /**
9
+ * Create a storage instance
10
+ * @param {Storage} storage - localStorage or sessionStorage
11
+ * @param {String} prefix - Key prefix for namespacing
12
+ */
13
+ function createStorage(storage, prefix = '') {
14
+ return {
15
+ /**
16
+ * Set item
17
+ */
18
+ set(key, value) {
19
+ try {
20
+ const fullKey = prefix + key;
21
+ const serialized = JSON.stringify(value);
22
+ storage.setItem(fullKey, serialized);
23
+ return true;
24
+ } catch (error) {
25
+ console.error('[Storage] Set failed:', error);
26
+ return false;
27
+ }
28
+ },
29
+
30
+ /**
31
+ * Get item
32
+ */
33
+ get(key, defaultValue = null) {
34
+ try {
35
+ const fullKey = prefix + key;
36
+ const item = storage.getItem(fullKey);
37
+
38
+ if (item === null) return defaultValue;
39
+
40
+ return JSON.parse(item);
41
+ } catch (error) {
42
+ console.error('[Storage] Get failed:', error);
43
+ return defaultValue;
44
+ }
45
+ },
46
+
47
+ /**
48
+ * Remove item
49
+ */
50
+ remove(key) {
51
+ try {
52
+ const fullKey = prefix + key;
53
+ storage.removeItem(fullKey);
54
+ return true;
55
+ } catch (error) {
56
+ console.error('[Storage] Remove failed:', error);
57
+ return false;
58
+ }
59
+ },
60
+
61
+ /**
62
+ * Clear all items (with prefix)
63
+ */
64
+ clear() {
65
+ try {
66
+ if (prefix) {
67
+ // Clear only items with prefix
68
+ const keys = Object.keys(storage);
69
+ keys.forEach(key => {
70
+ if (key.startsWith(prefix)) {
71
+ storage.removeItem(key);
72
+ }
73
+ });
74
+ } else {
75
+ // Clear everything
76
+ storage.clear();
77
+ }
78
+ return true;
79
+ } catch (error) {
80
+ console.error('[Storage] Clear failed:', error);
81
+ return false;
82
+ }
83
+ },
84
+
85
+ /**
86
+ * Get all keys
87
+ */
88
+ keys() {
89
+ const allKeys = Object.keys(storage);
90
+ return allKeys
91
+ .filter(key => !prefix || key.startsWith(prefix))
92
+ .map(key => prefix ? key.slice(prefix.length) : key);
93
+ },
94
+
95
+ /**
96
+ * Check if key exists
97
+ */
98
+ has(key) {
99
+ const fullKey = prefix + key;
100
+ return storage.getItem(fullKey) !== null;
101
+ },
102
+
103
+ /**
104
+ * Get multiple items
105
+ */
106
+ getMultiple(keys) {
107
+ return keys.reduce((result, key) => {
108
+ result[key] = this.get(key);
109
+ return result;
110
+ }, {});
111
+ },
112
+
113
+ /**
114
+ * Set multiple items
115
+ */
116
+ setMultiple(items) {
117
+ Object.keys(items).forEach(key => {
118
+ this.set(key, items[key]);
119
+ });
120
+ }
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Local storage (persists across sessions)
126
+ */
127
+ export const local = createStorage(localStorage);
128
+
129
+ /**
130
+ * Session storage (cleared when browser closes)
131
+ */
132
+ export const session = createStorage(sessionStorage);
133
+
134
+ /**
135
+ * Create namespaced storage
136
+ */
137
+ export function createNamespace(namespace) {
138
+ return {
139
+ local: createStorage(localStorage, `${namespace}:`),
140
+ session: createStorage(sessionStorage, `${namespace}:`)
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Storage events - listen for changes
146
+ */
147
+ export function onStorageChange(callback) {
148
+ const handler = (event) => {
149
+ if (event.storageArea === localStorage) {
150
+ callback({
151
+ key: event.key,
152
+ oldValue: event.oldValue ? JSON.parse(event.oldValue) : null,
153
+ newValue: event.newValue ? JSON.parse(event.newValue) : null,
154
+ storage: 'local'
155
+ });
156
+ }
157
+ };
158
+
159
+ window.addEventListener('storage', handler);
160
+
161
+ return () => window.removeEventListener('storage', handler);
162
+ }
163
+
164
+ /**
165
+ * Example usage:
166
+ *
167
+ * import { local, session } from './storage.js';
168
+ *
169
+ * // Save data
170
+ * local.set('user', { name: 'John', age: 30 });
171
+ *
172
+ * // Get data
173
+ * const user = local.get('user');
174
+ *
175
+ * // With default value
176
+ * const settings = local.get('settings', { theme: 'light' });
177
+ *
178
+ * // Namespaced storage
179
+ * const appStorage = createNamespace('myapp');
180
+ * appStorage.local.set('data', { ... });
181
+ */
@@ -0,0 +1,193 @@
1
+ /**
2
+ * FORM VALIDATION PLUGIN
3
+ *
4
+ * Simple, human-readable validation rules for forms.
5
+ */
6
+
7
+ /**
8
+ * Validation rules
9
+ */
10
+ export const rules = {
11
+ required: (value) => {
12
+ if (value === null || value === undefined || value === '') {
13
+ return 'This field is required';
14
+ }
15
+ return true;
16
+ },
17
+
18
+ email: (value) => {
19
+ if (!value) return true; // Use with required for mandatory
20
+ const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
21
+ return regex.test(value) || 'Invalid email address';
22
+ },
23
+
24
+ minLength: (min) => (value) => {
25
+ if (!value) return true;
26
+ return value.length >= min || `Minimum ${min} characters required`;
27
+ },
28
+
29
+ maxLength: (max) => (value) => {
30
+ if (!value) return true;
31
+ return value.length <= max || `Maximum ${max} characters allowed`;
32
+ },
33
+
34
+ min: (minValue) => (value) => {
35
+ const num = Number(value);
36
+ return num >= minValue || `Minimum value is ${minValue}`;
37
+ },
38
+
39
+ max: (maxValue) => (value) => {
40
+ const num = Number(value);
41
+ return num <= maxValue || `Maximum value is ${maxValue}`;
42
+ },
43
+
44
+ pattern: (regex, message = 'Invalid format') => (value) => {
45
+ if (!value) return true;
46
+ return regex.test(value) || message;
47
+ },
48
+
49
+ url: (value) => {
50
+ if (!value) return true;
51
+ try {
52
+ new URL(value);
53
+ return true;
54
+ } catch {
55
+ return 'Invalid URL';
56
+ }
57
+ },
58
+
59
+ number: (value) => {
60
+ if (!value) return true;
61
+ return !isNaN(value) || 'Must be a number';
62
+ },
63
+
64
+ integer: (value) => {
65
+ if (!value) return true;
66
+ return Number.isInteger(Number(value)) || 'Must be an integer';
67
+ },
68
+
69
+ match: (otherField, fieldName) => (value, allValues) => {
70
+ return value === allValues[otherField] || `Must match ${fieldName}`;
71
+ },
72
+
73
+ custom: (fn, message) => (value, allValues) => {
74
+ return fn(value, allValues) || message;
75
+ }
76
+ };
77
+
78
+ /**
79
+ * Create a validator
80
+ * @param {Object} schema - Validation schema { field: [rules] }
81
+ */
82
+ export function createValidator(schema) {
83
+ return {
84
+ /**
85
+ * Validate all fields
86
+ */
87
+ validate(values) {
88
+ const errors = {};
89
+
90
+ Object.keys(schema).forEach(field => {
91
+ const fieldRules = schema[field];
92
+ const value = values[field];
93
+
94
+ for (const rule of fieldRules) {
95
+ const result = rule(value, values);
96
+ if (result !== true) {
97
+ errors[field] = result;
98
+ break; // Stop at first error
99
+ }
100
+ }
101
+ });
102
+
103
+ return {
104
+ isValid: Object.keys(errors).length === 0,
105
+ errors
106
+ };
107
+ },
108
+
109
+ /**
110
+ * Validate single field
111
+ */
112
+ validateField(field, value, allValues = {}) {
113
+ const fieldRules = schema[field];
114
+ if (!fieldRules) return { isValid: true, error: null };
115
+
116
+ for (const rule of fieldRules) {
117
+ const result = rule(value, allValues);
118
+ if (result !== true) {
119
+ return { isValid: false, error: result };
120
+ }
121
+ }
122
+
123
+ return { isValid: true, error: null };
124
+ }
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Form helper - extracts form data
130
+ */
131
+ export function getFormData(formElement) {
132
+ const formData = new FormData(formElement);
133
+ const data = {};
134
+
135
+ for (const [key, value] of formData.entries()) {
136
+ // Handle multiple checkboxes with same name
137
+ if (data[key]) {
138
+ if (Array.isArray(data[key])) {
139
+ data[key].push(value);
140
+ } else {
141
+ data[key] = [data[key], value];
142
+ }
143
+ } else {
144
+ data[key] = value;
145
+ }
146
+ }
147
+
148
+ return data;
149
+ }
150
+
151
+ /**
152
+ * Display validation errors in form
153
+ */
154
+ export function displayErrors(formElement, errors) {
155
+ // Clear previous errors
156
+ formElement.querySelectorAll('.error-message').forEach(el => el.remove());
157
+ formElement.querySelectorAll('.error').forEach(el => el.classList.remove('error'));
158
+
159
+ // Display new errors
160
+ Object.keys(errors).forEach(field => {
161
+ const input = formElement.querySelector(`[name="${field}"]`);
162
+ if (!input) return;
163
+
164
+ // Add error class
165
+ input.classList.add('error');
166
+
167
+ // Create error message
168
+ const errorDiv = document.createElement('div');
169
+ errorDiv.className = 'error-message';
170
+ errorDiv.style.color = 'red';
171
+ errorDiv.style.fontSize = '14px';
172
+ errorDiv.style.marginTop = '4px';
173
+ errorDiv.textContent = errors[field];
174
+
175
+ // Insert after input
176
+ input.parentNode.insertBefore(errorDiv, input.nextSibling);
177
+ });
178
+ }
179
+
180
+ /**
181
+ * Example usage:
182
+ *
183
+ * const validator = createValidator({
184
+ * email: [rules.required, rules.email],
185
+ * password: [rules.required, rules.minLength(8)],
186
+ * age: [rules.required, rules.number, rules.min(18)]
187
+ * });
188
+ *
189
+ * const result = validator.validate(formData);
190
+ * if (!result.isValid) {
191
+ * displayErrors(formElement, result.errors);
192
+ * }
193
+ */
File without changes