@lengkapp/edge 0.0.4 → 0.0.6
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/edge-client.js +277 -250
- package/edge-server.d.ts +128 -63
- package/edge-server.js +122 -37
- package/package.json +3 -15
- package/client.min.d.ts +0 -3
- package/edge-client.min.js +0 -1
- package/edge-server.min.js +0 -12
- package/server.min.d.ts +0 -83
package/edge-client.js
CHANGED
|
@@ -1,279 +1,306 @@
|
|
|
1
|
-
(()
|
|
2
|
-
|
|
1
|
+
(function () {
|
|
2
|
+
if (window.__edge) return;
|
|
3
|
+
window.__edge = true;
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
body = d.body,
|
|
6
|
-
head = d.head;
|
|
5
|
+
var d = document, head = d.head, root = d.documentElement;
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
'focus', 'blur', 'focusin', 'focusout',
|
|
13
|
-
'keydown', 'keyup', 'keypress',
|
|
14
|
-
'change', 'input', 'submit', 'reset',
|
|
15
|
-
'load', 'DOMContentLoaded', 'ready',
|
|
16
|
-
'scroll', 'resize', 'wheel', 'touchstart', 'touchend', 'touchmove',
|
|
17
|
-
'visible', 'intersect'
|
|
18
|
-
]);
|
|
7
|
+
var METHODS = ['get', 'post', 'put', 'patch', 'delete'];
|
|
8
|
+
var METHOD_ATTRS = METHODS.map(function (m) { return '_' + m; });
|
|
9
|
+
var HAS_BODY = { post: 1, put: 1, patch: 1 };
|
|
10
|
+
var SEL = METHOD_ATTRS.map(function (a) { return '[' + a + ']'; }).join(',');
|
|
19
11
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
var DELEGATABLE = new Set([
|
|
13
|
+
'click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout',
|
|
14
|
+
'mousemove', 'contextmenu', 'focusin', 'focusout', 'keydown', 'keyup',
|
|
15
|
+
'keypress', 'change', 'input', 'submit', 'reset', 'wheel',
|
|
16
|
+
'touchstart', 'touchend', 'touchmove'
|
|
17
|
+
]);
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
.df-skeleton .df-bar {
|
|
33
|
-
height:12px; background:linear-gradient(90deg,#e0e0e0 25%,#f0f0f0 50%,#e0e0e0 75%);
|
|
34
|
-
background-size:200% 100%; animation:df-shimmer 1.5s infinite; border-radius:4px;
|
|
35
|
-
}
|
|
36
|
-
@keyframes df-shimmer { 0%{background-position:-200% 0} 100%{background-position:200% 0} }
|
|
37
|
-
.df-retry {
|
|
38
|
-
display:inline-block; padding:8px 16px; background:#007bff; color:#fff;
|
|
39
|
-
border-radius:4px; cursor:pointer; text-decoration:none; font-size:14px;
|
|
40
|
-
}
|
|
41
|
-
.df-retry:hover { background:#0056b3; }
|
|
42
|
-
`;
|
|
43
|
-
head.appendChild(style);
|
|
44
|
-
};
|
|
45
|
-
injectBaseStyles();
|
|
19
|
+
var bound = new WeakSet();
|
|
20
|
+
var running = new WeakSet();
|
|
21
|
+
var targetCache = new WeakMap();
|
|
22
|
+
var wasVisible = new WeakMap();
|
|
23
|
+
var ioObservers = new Map();
|
|
24
|
+
var injected = new Set();
|
|
25
|
+
var executedSrc = new Set();
|
|
46
26
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
27
|
+
var style = d.createElement('style');
|
|
28
|
+
style.textContent =
|
|
29
|
+
'.aj-skel-wrap{box-sizing:border-box;width:100%;height:100%;min-width:80px;min-height:48px;' +
|
|
30
|
+
'max-width:100%;max-height:600px;display:flex;align-items:center;justify-content:center}' +
|
|
31
|
+
'.aj-skel-wrap svg{width:1.75em;height:1.75em;color:#c7c7c7;' +
|
|
32
|
+
'animation:aj-pulse 1.4s ease-in-out infinite}' +
|
|
33
|
+
'@keyframes aj-pulse{0%,100%{opacity:.35;transform:scale(.92)}50%{opacity:1;transform:scale(1)}}' +
|
|
34
|
+
'.aj-retry-wrap{box-sizing:border-box;width:100%;height:100%;min-width:80px;min-height:48px;' +
|
|
35
|
+
'max-height:600px;display:flex;align-items:center;justify-content:center}' +
|
|
36
|
+
'.aj-retry{width:2.5em;height:2.5em;padding:0;cursor:pointer;background:#007bff;color:#fff;' +
|
|
37
|
+
'border:0;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;' +
|
|
38
|
+
'transition:background .15s ease}' +
|
|
39
|
+
'.aj-retry:hover{background:#0056b3}' +
|
|
40
|
+
'.aj-retry:hover svg{transform:rotate(-90deg)}' +
|
|
41
|
+
'.aj-retry svg{width:1.25em;height:1.25em;transition:transform .3s ease}';
|
|
42
|
+
head.appendChild(style);
|
|
57
43
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
44
|
+
function cfg(el) {
|
|
45
|
+
var attr = null;
|
|
46
|
+
for (var i = 0; i < METHOD_ATTRS.length; i++) {
|
|
47
|
+
if (el.hasAttribute(METHOD_ATTRS[i])) { attr = METHOD_ATTRS[i]; break; }
|
|
48
|
+
}
|
|
49
|
+
if (!attr) return null;
|
|
50
|
+
var method = attr.slice(1);
|
|
51
|
+
return {
|
|
52
|
+
url: el.getAttribute(attr),
|
|
53
|
+
method: method.toUpperCase(),
|
|
54
|
+
hasBody: !!HAS_BODY[method],
|
|
55
|
+
target: el.getAttribute('_target'),
|
|
56
|
+
trigger: el.getAttribute('_trigger'),
|
|
57
|
+
form: el.getAttribute('_form'),
|
|
58
|
+
json: el.getAttribute('_json'),
|
|
59
|
+
skeleton: el.getAttribute('_skeleton') !== 'false',
|
|
60
|
+
retry: el.getAttribute('_retry') !== 'false'
|
|
61
|
+
};
|
|
62
|
+
}
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
} else if (type === 'js') {
|
|
75
|
-
const script = d.createElement('script');
|
|
76
|
-
script.src = url;
|
|
77
|
-
body.appendChild(script);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// ---------- Visibility helpers ----------
|
|
83
|
-
const isElementActuallyVisible = (el) => {
|
|
84
|
-
const style = getComputedStyle(el);
|
|
85
|
-
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
|
|
86
|
-
const rect = el.getBoundingClientRect();
|
|
87
|
-
return rect.width > 0 && rect.height > 0;
|
|
88
|
-
};
|
|
64
|
+
function resolveTarget(el, c) {
|
|
65
|
+
if (!c.target || c.target === 'this') return el;
|
|
66
|
+
var cached = targetCache.get(el);
|
|
67
|
+
if (cached && cached.isConnected) return cached;
|
|
68
|
+
var found = d.querySelector(c.target);
|
|
69
|
+
if (found) targetCache.set(el, found);
|
|
70
|
+
return found;
|
|
71
|
+
}
|
|
89
72
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
intersectionObserver = new IntersectionObserver((entries) => {
|
|
95
|
-
entries.forEach(entry => {
|
|
96
|
-
const el = entry.target;
|
|
97
|
-
const visible = entry.isIntersecting && isElementActuallyVisible(el);
|
|
98
|
-
if (visible && el.dataset.wasVisible !== 'true') {
|
|
99
|
-
run(el);
|
|
100
|
-
}
|
|
101
|
-
el.dataset.wasVisible = visible ? 'true' : 'false';
|
|
102
|
-
});
|
|
103
|
-
}, { threshold: 0 });
|
|
104
|
-
}
|
|
105
|
-
return intersectionObserver;
|
|
106
|
-
};
|
|
73
|
+
function triggersFor(c) {
|
|
74
|
+
return (c.trigger || (c.target === 'this' ? 'load' : 'click'))
|
|
75
|
+
.split(',').map(function (s) { return s.trim().toLowerCase(); });
|
|
76
|
+
}
|
|
107
77
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
78
|
+
function isActuallyVisible(el) {
|
|
79
|
+
var s = getComputedStyle(el);
|
|
80
|
+
if (s.display === 'none' || s.visibility === 'hidden' || s.opacity === '0') return false;
|
|
81
|
+
var r = el.getBoundingClientRect();
|
|
82
|
+
return r.width > 0 && r.height > 0;
|
|
83
|
+
}
|
|
112
84
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
85
|
+
var SKELETON_ICON =
|
|
86
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" ' +
|
|
87
|
+
'fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" ' +
|
|
88
|
+
'stroke-linejoin="round" aria-hidden="true">' +
|
|
89
|
+
'<path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/>' +
|
|
90
|
+
'</svg>';
|
|
116
91
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (formId) {
|
|
121
|
-
const form = d.getElementById(formId);
|
|
122
|
-
if (form) bodyData = new URLSearchParams(new FormData(form));
|
|
123
|
-
} else if (json) {
|
|
124
|
-
bodyData = JSON.stringify(Object.fromEntries(
|
|
125
|
-
json.split(',').map(name => {
|
|
126
|
-
const input = d.querySelector(`[name="${name}"]`);
|
|
127
|
-
return [name, input ? input.value : ''];
|
|
128
|
-
})
|
|
129
|
-
));
|
|
130
|
-
headers['Content-Type'] = 'application/json';
|
|
131
|
-
}
|
|
132
|
-
}
|
|
92
|
+
function showSkeleton(el) {
|
|
93
|
+
el.innerHTML = '<div class="aj-skel-wrap">' + SKELETON_ICON + '</div>';
|
|
94
|
+
}
|
|
133
95
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
if (!container) {
|
|
142
|
-
console.warn('Target not found:', targetSelector);
|
|
143
|
-
el.dataset.running = 'false';
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
96
|
+
var RETRY_ICON =
|
|
97
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" ' +
|
|
98
|
+
'fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" ' +
|
|
99
|
+
'stroke-linejoin="round" aria-hidden="true">' +
|
|
100
|
+
'<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/>' +
|
|
101
|
+
'</svg>';
|
|
146
102
|
|
|
147
|
-
|
|
148
|
-
|
|
103
|
+
function showRetry(el, fn) {
|
|
104
|
+
el.innerHTML = '';
|
|
105
|
+
var wrap = d.createElement('div');
|
|
106
|
+
wrap.className = 'aj-retry-wrap';
|
|
107
|
+
var b = d.createElement('button');
|
|
108
|
+
b.type = 'button';
|
|
109
|
+
b.className = 'aj-retry';
|
|
110
|
+
b.setAttribute('aria-label', 'Retry');
|
|
111
|
+
b.innerHTML = RETRY_ICON;
|
|
112
|
+
b.addEventListener('click', fn, { once: true });
|
|
113
|
+
wrap.appendChild(b);
|
|
114
|
+
el.appendChild(wrap);
|
|
115
|
+
}
|
|
149
116
|
|
|
150
|
-
|
|
151
|
-
|
|
117
|
+
function parseList(raw) {
|
|
118
|
+
if (!raw) return [];
|
|
119
|
+
return raw.replace(/^\[|\]$/g, '').split(',')
|
|
120
|
+
.map(function (s) { return s.trim().replace(/^['"]|['"]$/g, ''); })
|
|
121
|
+
.filter(Boolean);
|
|
122
|
+
}
|
|
152
123
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
});
|
|
170
|
-
container.replaceChildren(retry);
|
|
171
|
-
} finally {
|
|
172
|
-
el.dataset.running = 'false';
|
|
173
|
-
}
|
|
174
|
-
};
|
|
124
|
+
function injectResources(headers) {
|
|
125
|
+
parseList(headers.get('x-css-required')).forEach(function (url) {
|
|
126
|
+
if (injected.has(url)) return;
|
|
127
|
+
injected.add(url);
|
|
128
|
+
var link = d.createElement('link');
|
|
129
|
+
link.rel = 'stylesheet'; link.href = url;
|
|
130
|
+
head.appendChild(link);
|
|
131
|
+
});
|
|
132
|
+
parseList(headers.get('x-js-required')).forEach(function (url) {
|
|
133
|
+
if (injected.has(url)) return;
|
|
134
|
+
injected.add(url);
|
|
135
|
+
var s = d.createElement('script');
|
|
136
|
+
s.src = url;
|
|
137
|
+
head.appendChild(s);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
175
140
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
141
|
+
function execScripts(container) {
|
|
142
|
+
var scripts = Array.prototype.slice.call(container.querySelectorAll('script'));
|
|
143
|
+
var i = 0;
|
|
144
|
+
function next() {
|
|
145
|
+
if (i >= scripts.length) return;
|
|
146
|
+
var old = scripts[i];
|
|
147
|
+
var s = d.createElement('script');
|
|
148
|
+
for (var a = 0; a < old.attributes.length; a++) {
|
|
149
|
+
s.setAttribute(old.attributes[a].name, old.attributes[a].value);
|
|
150
|
+
}
|
|
151
|
+
if (old.nonce) s.nonce = old.nonce;
|
|
152
|
+
if (old.src) {
|
|
153
|
+
if (executedSrc.has(old.src)) { old.remove(); i++; next(); return; }
|
|
154
|
+
executedSrc.add(old.src);
|
|
155
|
+
s.onload = s.onerror = function () { i++; next(); };
|
|
156
|
+
old.replaceWith(s);
|
|
157
|
+
} else {
|
|
158
|
+
s.textContent = '{\n' + old.textContent + '\n}';
|
|
159
|
+
old.replaceWith(s);
|
|
160
|
+
i++; next();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
next();
|
|
164
|
+
}
|
|
195
165
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
166
|
+
function setContent(el, html) {
|
|
167
|
+
el.innerHTML = html;
|
|
168
|
+
execScripts(el);
|
|
169
|
+
bind(el);
|
|
170
|
+
}
|
|
200
171
|
|
|
201
|
-
|
|
172
|
+
function doFetch(el, c) {
|
|
173
|
+
if (running.has(el)) return;
|
|
174
|
+
running.add(el);
|
|
202
175
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
// Set up visibility observation
|
|
208
|
-
if (events.has('visible') || events.has('intersect')) {
|
|
209
|
-
el.dataset.wasVisible = 'false';
|
|
210
|
-
getIO().observe(el);
|
|
211
|
-
}
|
|
212
|
-
// All other events are handled by the global delegated listener
|
|
213
|
-
};
|
|
176
|
+
var target = resolveTarget(el, c);
|
|
177
|
+
if (!target) { running.delete(el); return; }
|
|
178
|
+
if (c.skeleton) showSkeleton(target);
|
|
214
179
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
'
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
180
|
+
var opts = { method: c.method };
|
|
181
|
+
if (c.hasBody) {
|
|
182
|
+
if (c.json) {
|
|
183
|
+
var obj = {};
|
|
184
|
+
c.json.split(',').forEach(function (name) {
|
|
185
|
+
name = name.trim();
|
|
186
|
+
var input = d.querySelector('[name="' + name + '"]');
|
|
187
|
+
obj[name] = input ? input.value : undefined;
|
|
188
|
+
});
|
|
189
|
+
opts.headers = { 'Content-Type': 'application/json' };
|
|
190
|
+
opts.body = JSON.stringify(obj);
|
|
191
|
+
} else if (c.form) {
|
|
192
|
+
var form = d.getElementById(c.form);
|
|
193
|
+
opts.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
|
194
|
+
opts.body = form ? new URLSearchParams(new FormData(form)).toString() : '';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
224
197
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}, isPassive ? { passive: true } : false);
|
|
239
|
-
});
|
|
198
|
+
fetch(c.url, opts)
|
|
199
|
+
.then(function (res) {
|
|
200
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
201
|
+
injectResources(res.headers);
|
|
202
|
+
return res.text();
|
|
203
|
+
})
|
|
204
|
+
.then(function (html) { setContent(target, html); })
|
|
205
|
+
.catch(function (err) {
|
|
206
|
+
console.error(err);
|
|
207
|
+
if (c.retry) showRetry(target, function () { doFetch(el, c); });
|
|
208
|
+
})
|
|
209
|
+
.finally(function () { running.delete(el); });
|
|
210
|
+
}
|
|
240
211
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
212
|
+
var delegationInit = false;
|
|
213
|
+
function initDelegation() {
|
|
214
|
+
if (delegationInit) return;
|
|
215
|
+
delegationInit = true;
|
|
216
|
+
DELEGATABLE.forEach(function (evt) {
|
|
217
|
+
var isClickOrSubmit = evt === 'click' || evt === 'submit';
|
|
218
|
+
d.addEventListener(evt, function (e) {
|
|
219
|
+
var t = e.target;
|
|
220
|
+
if (!(t instanceof Element)) return;
|
|
221
|
+
var el = t.closest(SEL);
|
|
222
|
+
if (!el) return;
|
|
223
|
+
var c = cfg(el);
|
|
224
|
+
if (!c || triggersFor(c).indexOf(evt) === -1) return;
|
|
225
|
+
if (isClickOrSubmit) e.preventDefault();
|
|
226
|
+
doFetch(el, c);
|
|
227
|
+
}, isClickOrSubmit ? false : { passive: true });
|
|
228
|
+
});
|
|
229
|
+
}
|
|
244
230
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (node.nodeType !== 1) return;
|
|
251
|
-
if (node.matches('[_get], [_post]')) initElement(node);
|
|
252
|
-
node.querySelectorAll('[_get], [_post]').forEach(initElement);
|
|
253
|
-
});
|
|
254
|
-
};
|
|
231
|
+
function bindOne(el) {
|
|
232
|
+
if (bound.has(el)) return;
|
|
233
|
+
bound.add(el);
|
|
234
|
+
var c = cfg(el);
|
|
235
|
+
if (!c) return;
|
|
255
236
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
237
|
+
triggersFor(c).forEach(function (trig) {
|
|
238
|
+
if (trig === 'load' || trig === 'domcontentloaded' || trig === 'ready') {
|
|
239
|
+
queueMicrotask(function () { doFetch(el, c); });
|
|
240
|
+
} else if (trig === 'visible' || trig === 'intersect') {
|
|
241
|
+
wasVisible.set(el, false);
|
|
242
|
+
var obs = new IntersectionObserver(function (entries) {
|
|
243
|
+
entries.forEach(function (entry) {
|
|
244
|
+
if (!entry.target.isConnected) { obs.disconnect(); ioObservers.delete(el); return; }
|
|
245
|
+
var vis = entry.isIntersecting && isActuallyVisible(el);
|
|
246
|
+
if (vis && !wasVisible.get(el)) doFetch(el, c);
|
|
247
|
+
wasVisible.set(el, vis);
|
|
260
248
|
});
|
|
261
249
|
});
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
250
|
+
obs.observe(el);
|
|
251
|
+
ioObservers.set(el, obs);
|
|
252
|
+
} else if (!DELEGATABLE.has(trig)) {
|
|
253
|
+
el.addEventListener(trig, function () { doFetch(el, c); });
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
268
257
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
258
|
+
function bind(subtreeRoot) {
|
|
259
|
+
if (subtreeRoot.nodeType !== 1) return;
|
|
260
|
+
if (subtreeRoot.matches && subtreeRoot.matches(SEL)) bindOne(subtreeRoot);
|
|
261
|
+
if (subtreeRoot.querySelectorAll) subtreeRoot.querySelectorAll(SEL).forEach(bindOne);
|
|
262
|
+
}
|
|
273
263
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
264
|
+
function cleanup(removedRoot) {
|
|
265
|
+
if (!ioObservers.size) return;
|
|
266
|
+
ioObservers.forEach(function (obs, el) {
|
|
267
|
+
if (removedRoot === el || removedRoot.contains(el)) {
|
|
268
|
+
obs.disconnect();
|
|
269
|
+
ioObservers.delete(el);
|
|
270
|
+
bound.delete(el);
|
|
278
271
|
}
|
|
279
|
-
})
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
var addQueue = new Set(), removeQueue = new Set(), flushScheduled = false;
|
|
276
|
+
function flush() {
|
|
277
|
+
flushScheduled = false;
|
|
278
|
+
var adds = Array.from(addQueue); addQueue.clear();
|
|
279
|
+
var rems = Array.from(removeQueue); removeQueue.clear();
|
|
280
|
+
adds.forEach(bind);
|
|
281
|
+
rems.forEach(cleanup);
|
|
282
|
+
}
|
|
283
|
+
function scheduleFlush() {
|
|
284
|
+
if (flushScheduled) return;
|
|
285
|
+
flushScheduled = true;
|
|
286
|
+
queueMicrotask(flush);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
new MutationObserver(function (mutations) {
|
|
290
|
+
mutations.forEach(function (m) {
|
|
291
|
+
m.addedNodes.forEach(function (n) { if (n.nodeType === 1) addQueue.add(n); });
|
|
292
|
+
m.removedNodes.forEach(function (n) { if (n.nodeType === 1) removeQueue.add(n); });
|
|
293
|
+
});
|
|
294
|
+
scheduleFlush();
|
|
295
|
+
}).observe(root, { childList: true, subtree: true });
|
|
296
|
+
|
|
297
|
+
function initAll() {
|
|
298
|
+
initDelegation();
|
|
299
|
+
bind(root);
|
|
300
|
+
}
|
|
301
|
+
if (d.readyState === 'loading') {
|
|
302
|
+
d.addEventListener('DOMContentLoaded', initAll, { once: true });
|
|
303
|
+
} else {
|
|
304
|
+
initAll();
|
|
305
|
+
}
|
|
306
|
+
})();
|
package/edge-server.d.ts
CHANGED
|
@@ -1,83 +1,148 @@
|
|
|
1
|
-
|
|
1
|
+
// Type definitions for edge-server
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
props: Record<string, any>;
|
|
6
|
-
children: any[];
|
|
7
|
-
__isJSX: true;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function jsx(
|
|
11
|
-
type: any,
|
|
12
|
-
props?: Record<string, any> | null,
|
|
13
|
-
...children: any[]
|
|
14
|
-
): JSXNode;
|
|
15
|
-
|
|
16
|
-
export function renderToString(node: any): string;
|
|
17
|
-
|
|
18
|
-
export interface RouteOptions {
|
|
19
|
-
auth?: boolean | { role?: string; scopes?: string[] };
|
|
20
|
-
rateLimit?: boolean | { max?: number; window?: number };
|
|
21
|
-
cors?: boolean | { origin?: string; methods?: string; headers?: string };
|
|
22
|
-
validate?: (ctx: Context) => boolean | Promise<boolean>;
|
|
23
|
-
log?: boolean;
|
|
24
|
-
cache?: boolean | { ttl?: number; staleWhileRevalidate?: number };
|
|
25
|
-
compress?: boolean;
|
|
26
|
-
}
|
|
3
|
+
/// <reference lib="dom" />
|
|
4
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
27
5
|
|
|
28
6
|
export class Context {
|
|
7
|
+
constructor(
|
|
8
|
+
request: Request,
|
|
9
|
+
env: any,
|
|
10
|
+
executionCtx: ExecutionContext,
|
|
11
|
+
params?: Record<string, string>,
|
|
12
|
+
parsedUrl?: URL | null
|
|
13
|
+
);
|
|
14
|
+
|
|
29
15
|
req: Request;
|
|
30
16
|
env: any;
|
|
31
17
|
executionCtx: ExecutionContext;
|
|
32
18
|
params: Record<string, string>;
|
|
33
19
|
status: number;
|
|
34
20
|
headers: Headers;
|
|
21
|
+
url: URL;
|
|
35
22
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
executionCtx: ExecutionContext,
|
|
40
|
-
params?: Record<string, string>,
|
|
41
|
-
parsedUrl?: URL
|
|
42
|
-
);
|
|
23
|
+
// Private members (not accessible, but present)
|
|
24
|
+
private _rawCookie: string;
|
|
25
|
+
private _cookies: Record<string, string> | null;
|
|
43
26
|
|
|
44
27
|
getCookie(name: string): string | null;
|
|
45
|
-
|
|
46
|
-
setCookie(
|
|
47
|
-
|
|
28
|
+
readonly query: URLSearchParams;
|
|
29
|
+
setCookie(
|
|
30
|
+
name: string,
|
|
31
|
+
value: string,
|
|
32
|
+
options?: {
|
|
33
|
+
path?: string;
|
|
34
|
+
domain?: string;
|
|
35
|
+
maxAge?: number;
|
|
36
|
+
expires?: Date;
|
|
37
|
+
secure?: boolean;
|
|
38
|
+
httpOnly?: boolean;
|
|
39
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
40
|
+
}
|
|
41
|
+
): void;
|
|
42
|
+
deleteCookie(
|
|
43
|
+
name: string,
|
|
44
|
+
options?: {
|
|
45
|
+
path?: string;
|
|
46
|
+
domain?: string;
|
|
47
|
+
secure?: boolean;
|
|
48
|
+
httpOnly?: boolean;
|
|
49
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
50
|
+
}
|
|
51
|
+
): void;
|
|
52
|
+
|
|
48
53
|
text(data: string, status?: number, headers?: Record<string, string>): Response;
|
|
49
54
|
json(data: any, status?: number, headers?: Record<string, string>): Response;
|
|
50
55
|
html(data: string, status?: number, headers?: Record<string, string>): Response;
|
|
56
|
+
|
|
57
|
+
private _buildHeaders(headers: Record<string, string>): Headers;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const Fragment: unique symbol;
|
|
61
|
+
|
|
62
|
+
export interface JSXElement {
|
|
63
|
+
type: any;
|
|
64
|
+
props: Record<string, any>;
|
|
65
|
+
children: any[];
|
|
66
|
+
__isJSX: true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function jsx(type: any, props: Record<string, any> | null, ...children: any[]): JSXElement;
|
|
70
|
+
|
|
71
|
+
export function renderToString(node: any): string;
|
|
72
|
+
|
|
73
|
+
// Route option interfaces
|
|
74
|
+
|
|
75
|
+
interface AuthOptions {
|
|
76
|
+
role?: string;
|
|
77
|
+
scopes?: string[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface RateLimitOptions {
|
|
81
|
+
max?: number;
|
|
82
|
+
window?: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface CorsOptions {
|
|
86
|
+
origin?: string;
|
|
87
|
+
methods?: string;
|
|
88
|
+
headers?: string;
|
|
89
|
+
credentials?: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface CacheOptions {
|
|
93
|
+
ttl?: number;
|
|
94
|
+
staleWhileRevalidate?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface RouteOptions {
|
|
98
|
+
auth?: boolean | AuthOptions;
|
|
99
|
+
rateLimit?: boolean | RateLimitOptions;
|
|
100
|
+
cors?: boolean | CorsOptions;
|
|
101
|
+
validate?: (ctx: Context) => boolean | Promise<boolean>;
|
|
102
|
+
cache?: boolean | CacheOptions;
|
|
103
|
+
compress?: boolean;
|
|
104
|
+
log?: boolean;
|
|
51
105
|
}
|
|
52
106
|
|
|
107
|
+
type RouteHandler = (ctx: Context) => Response | JSXElement | any | Promise<Response | JSXElement | any>;
|
|
108
|
+
|
|
53
109
|
export class Edge {
|
|
54
110
|
constructor();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
patch(path: string,
|
|
71
|
-
|
|
72
|
-
options(path: string, handler:
|
|
73
|
-
options(path: string, options: RouteOptions, handler:
|
|
74
|
-
|
|
75
|
-
head(path: string,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
111
|
+
|
|
112
|
+
// Route registration methods
|
|
113
|
+
get(path: string, handler: RouteHandler): void;
|
|
114
|
+
get(path: string, options: boolean | RouteOptions, handler: RouteHandler): void;
|
|
115
|
+
|
|
116
|
+
post(path: string, handler: RouteHandler): void;
|
|
117
|
+
post(path: string, options: boolean | RouteOptions, handler: RouteHandler): void;
|
|
118
|
+
|
|
119
|
+
put(path: string, handler: RouteHandler): void;
|
|
120
|
+
put(path: string, options: boolean | RouteOptions, handler: RouteHandler): void;
|
|
121
|
+
|
|
122
|
+
delete(path: string, handler: RouteHandler): void;
|
|
123
|
+
delete(path: string, options: boolean | RouteOptions, handler: RouteHandler): void;
|
|
124
|
+
|
|
125
|
+
patch(path: string, handler: RouteHandler): void;
|
|
126
|
+
patch(path: string, options: boolean | RouteOptions, handler: RouteHandler): void;
|
|
127
|
+
|
|
128
|
+
options(path: string, handler: RouteHandler): void;
|
|
129
|
+
options(path: string, options: boolean | RouteOptions, handler: RouteHandler): void;
|
|
130
|
+
|
|
131
|
+
head(path: string, handler: RouteHandler): void;
|
|
132
|
+
head(path: string, options: boolean | RouteOptions, handler: RouteHandler): void;
|
|
133
|
+
|
|
134
|
+
scheduled(handler: (controller: ScheduledController, env: any, ctx: ExecutionContext) => void | Promise<void>): void;
|
|
135
|
+
|
|
136
|
+
// Main fetch handler
|
|
137
|
+
fetch(request: Request, env: any, executionCtx: ExecutionContext): Promise<Response>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// JSX namespace support
|
|
141
|
+
declare global {
|
|
142
|
+
namespace JSX {
|
|
143
|
+
interface Element extends JSXElement {}
|
|
144
|
+
interface IntrinsicElements {
|
|
145
|
+
[elemName: string]: any;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
83
148
|
}
|
package/edge-server.js
CHANGED
|
@@ -91,6 +91,8 @@ class Context {
|
|
|
91
91
|
|
|
92
92
|
export const Fragment = Symbol('Fragment');
|
|
93
93
|
|
|
94
|
+
// ---------- JSX Runtime ----------
|
|
95
|
+
|
|
94
96
|
export function jsx(type, props, ...children) {
|
|
95
97
|
const normalizedProps = props || {};
|
|
96
98
|
const flatChildren = children.flat(Infinity);
|
|
@@ -102,6 +104,70 @@ export function jsx(type, props, ...children) {
|
|
|
102
104
|
};
|
|
103
105
|
}
|
|
104
106
|
|
|
107
|
+
// HTML void elements that cannot have children
|
|
108
|
+
const VOID_ELEMENTS = new Set([
|
|
109
|
+
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
|
|
110
|
+
'link', 'meta', 'param', 'source', 'track', 'wbr'
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
// Boolean HTML attributes that should be rendered without a value when true
|
|
114
|
+
const BOOLEAN_ATTRIBUTES = new Set([
|
|
115
|
+
'allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked',
|
|
116
|
+
'controls', 'default', 'defer', 'disabled', 'formnovalidate',
|
|
117
|
+
'hidden', 'inert', 'ismap', 'itemscope', 'loop', 'multiple',
|
|
118
|
+
'muted', 'nomodule', 'novalidate', 'open', 'playsinline',
|
|
119
|
+
'readonly', 'required', 'reversed', 'selected'
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
// CSS properties that do not require a unit when numeric
|
|
123
|
+
const UNITLESS_PROPERTIES = new Set([
|
|
124
|
+
'animation-iteration-count', 'border-image-outset', 'border-image-slice',
|
|
125
|
+
'border-image-width', 'box-flex', 'box-flex-group', 'box-ordinal-group',
|
|
126
|
+
'column-count', 'columns', 'flex', 'flex-grow', 'flex-positive',
|
|
127
|
+
'flex-shrink', 'flex-negative', 'flex-order', 'grid-row', 'grid-row-end',
|
|
128
|
+
'grid-row-span', 'grid-row-start', 'grid-column', 'grid-column-end',
|
|
129
|
+
'grid-column-span', 'grid-column-start', 'font-weight', 'line-clamp',
|
|
130
|
+
'line-height', 'opacity', 'order', 'orphans', 'tab-size', 'widows',
|
|
131
|
+
'z-index', 'zoom', 'fill-opacity', 'flood-opacity', 'stop-opacity',
|
|
132
|
+
'stroke-dasharray', 'stroke-dashoffset', 'stroke-miterlimit',
|
|
133
|
+
'stroke-opacity', 'stroke-width'
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
function escapeHtml(str) {
|
|
137
|
+
const HTML_ESCAPE_MAP = {
|
|
138
|
+
'&': '&',
|
|
139
|
+
'<': '<',
|
|
140
|
+
'>': '>',
|
|
141
|
+
'"': '"',
|
|
142
|
+
"'": '''
|
|
143
|
+
};
|
|
144
|
+
return str.replace(/[&<>"']/g, char => HTML_ESCAPE_MAP[char]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function camelToKebab(str) {
|
|
148
|
+
return str
|
|
149
|
+
.replace(/([A-Z])/g, '-$1')
|
|
150
|
+
.toLowerCase()
|
|
151
|
+
.replace(/^-/, '');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function styleObjectToString(style) {
|
|
155
|
+
if (!style || typeof style !== 'object') return '';
|
|
156
|
+
const entries = Object.entries(style);
|
|
157
|
+
if (entries.length === 0) return '';
|
|
158
|
+
return entries
|
|
159
|
+
.map(([prop, value]) => {
|
|
160
|
+
// Convert camelCase to kebab-case, handling vendor prefixes
|
|
161
|
+
let kebabProp = camelToKebab(prop);
|
|
162
|
+
// Add px unit for numeric values unless property is unitless
|
|
163
|
+
if (typeof value === 'number' && !UNITLESS_PROPERTIES.has(kebabProp)) {
|
|
164
|
+
value = `${value}px`;
|
|
165
|
+
}
|
|
166
|
+
return `${kebabProp}:${value}`;
|
|
167
|
+
})
|
|
168
|
+
.join(';');
|
|
169
|
+
}
|
|
170
|
+
|
|
105
171
|
export function renderToString(node) {
|
|
106
172
|
if (node == null || typeof node === 'boolean') return '';
|
|
107
173
|
if (typeof node === 'string' || typeof node === 'number') {
|
|
@@ -137,53 +203,73 @@ export function renderToString(node) {
|
|
|
137
203
|
return renderToString(componentResult);
|
|
138
204
|
}
|
|
139
205
|
|
|
206
|
+
// Handle void elements: they cannot have children
|
|
207
|
+
const isVoid = VOID_ELEMENTS.has(type);
|
|
208
|
+
|
|
140
209
|
const attrsParts = [];
|
|
141
210
|
for (const key in props) {
|
|
142
|
-
if (key === 'children') continue;
|
|
211
|
+
if (key === 'children' || key === 'key' || key === 'ref') continue;
|
|
143
212
|
const value = props[key];
|
|
213
|
+
|
|
214
|
+
// Skip null/undefined/false
|
|
144
215
|
if (value == null || value === false) continue;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
216
|
+
|
|
217
|
+
// dangerouslySetInnerHTML will be handled separately
|
|
218
|
+
if (key === 'dangerouslySetInnerHTML') continue;
|
|
219
|
+
|
|
220
|
+
// Attribute name mapping
|
|
221
|
+
let attrName = key;
|
|
222
|
+
if (key === 'className' || key === 'class') {
|
|
223
|
+
attrName = 'class';
|
|
224
|
+
} else if (key === 'htmlFor' || key === 'for') {
|
|
225
|
+
attrName = 'for';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Boolean attributes: if true, just the attribute name; if false, skip
|
|
229
|
+
if (BOOLEAN_ATTRIBUTES.has(attrName)) {
|
|
230
|
+
if (value === true) {
|
|
231
|
+
attrsParts.push(` ${attrName}`);
|
|
232
|
+
}
|
|
150
233
|
continue;
|
|
151
|
-
} else if (key === 'style' && typeof value === 'object') {
|
|
152
|
-
const styleStr = Object.entries(value)
|
|
153
|
-
.map(([prop, val]) => `${camelToKebab(prop)}:${val}`)
|
|
154
|
-
.join(';');
|
|
155
|
-
attrsParts.push(` style="${escapeHtml(styleStr)}"`);
|
|
156
|
-
} else if (value === true) {
|
|
157
|
-
attrsParts.push(` ${key}`);
|
|
158
|
-
} else {
|
|
159
|
-
attrsParts.push(` ${key}="${escapeHtml(String(value))}"`);
|
|
160
234
|
}
|
|
161
|
-
}
|
|
162
|
-
const attrs = attrsParts.join('');
|
|
163
235
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
236
|
+
// Non-boolean attribute with true -> render as "true"
|
|
237
|
+
if (value === true) {
|
|
238
|
+
attrsParts.push(` ${attrName}="true"`);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
169
241
|
|
|
170
|
-
|
|
171
|
-
|
|
242
|
+
// Style object handling
|
|
243
|
+
if (attrName === 'style' && typeof value === 'object') {
|
|
244
|
+
const styleStr = styleObjectToString(value);
|
|
245
|
+
if (styleStr) {
|
|
246
|
+
attrsParts.push(` style="${escapeHtml(styleStr)}"`);
|
|
247
|
+
}
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
172
250
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
'
|
|
177
|
-
'"': '"',
|
|
178
|
-
"'": '''
|
|
179
|
-
};
|
|
251
|
+
// Regular attribute
|
|
252
|
+
attrsParts.push(` ${attrName}="${escapeHtml(String(value))}"`);
|
|
253
|
+
}
|
|
254
|
+
const attrs = attrsParts.join('');
|
|
180
255
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
256
|
+
// Handle dangerouslySetInnerHTML
|
|
257
|
+
let innerHTML = '';
|
|
258
|
+
if (props && props.dangerouslySetInnerHTML && props.dangerouslySetInnerHTML.__html != null) {
|
|
259
|
+
innerHTML = props.dangerouslySetInnerHTML.__html;
|
|
260
|
+
} else {
|
|
261
|
+
const childParts = [];
|
|
262
|
+
for (let i = 0; i < children.length; i++) {
|
|
263
|
+
childParts.push(renderToString(children[i]));
|
|
264
|
+
}
|
|
265
|
+
innerHTML = childParts.join('');
|
|
266
|
+
}
|
|
184
267
|
|
|
185
|
-
|
|
186
|
-
|
|
268
|
+
if (isVoid) {
|
|
269
|
+
return `<${type}${attrs}/>`;
|
|
270
|
+
} else {
|
|
271
|
+
return `<${type}${attrs}>${innerHTML}</${type}>`;
|
|
272
|
+
}
|
|
187
273
|
}
|
|
188
274
|
|
|
189
275
|
// ---------- Trie-based router for dynamic routes ----------
|
|
@@ -397,7 +483,6 @@ export class Edge {
|
|
|
397
483
|
const { ttl = 3600, staleWhileRevalidate = 0 } = options;
|
|
398
484
|
const cache = caches.default;
|
|
399
485
|
const responseClone = response.clone();
|
|
400
|
-
// Modify the clone's headers directly instead of creating a new Response
|
|
401
486
|
responseClone.headers.set('Cache-Control', `max-age=${ttl}${staleWhileRevalidate > 0 ? `, stale-while-revalidate=${staleWhileRevalidate}` : ''}`);
|
|
402
487
|
responseClone.headers.delete('Set-Cookie');
|
|
403
488
|
await cache.put(request, responseClone);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lengkapp/edge",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Edge framework used by Lengkapp",
|
|
5
5
|
"main": "edge-server.js",
|
|
6
6
|
"types": "./edge-server.d.ts",
|
|
@@ -18,25 +18,13 @@
|
|
|
18
18
|
"types": "./edge-server.d.ts",
|
|
19
19
|
"default": "./edge-server.js"
|
|
20
20
|
},
|
|
21
|
-
"./client.min": {
|
|
22
|
-
"types": "./client.min.d.ts",
|
|
23
|
-
"default": "./edge-client.min.js"
|
|
24
|
-
},
|
|
25
|
-
"./server.min": {
|
|
26
|
-
"types": "./server.min.d.ts",
|
|
27
|
-
"default": "./edge-server.min.js"
|
|
28
|
-
},
|
|
29
21
|
"./package.json": "./package.json"
|
|
30
22
|
},
|
|
31
23
|
"files": [
|
|
32
24
|
"edge-client.js",
|
|
33
|
-
"edge-client.min.js",
|
|
34
|
-
"edge-server.js",
|
|
35
|
-
"edge-server.min.js",
|
|
36
|
-
"edge-server.d.ts",
|
|
37
|
-
"server.min.d.ts",
|
|
38
25
|
"edge-client.d.ts",
|
|
39
|
-
"
|
|
26
|
+
"edge-server.js",
|
|
27
|
+
"edge-server.d.ts"
|
|
40
28
|
],
|
|
41
29
|
"scripts": {
|
|
42
30
|
"test": "echo \"No tests yet\""
|
package/client.min.d.ts
DELETED
package/edge-client.min.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(()=>{'use strict';const d=document,b=d.body,h=d.head,V=new Set(['click','dblclick','mousedown','mouseup','mouseover','mouseout','mouseenter','mouseleave','mousemove','contextmenu','focus','blur','focusin','focusout','keydown','keyup','keypress','change','input','submit','reset','load','DOMContentLoaded','ready','scroll','resize','wheel','touchstart','touchend','touchmove','visible','intersect']),T=new WeakMap,C=new WeakMap,R=new Set;if(!d.getElementById('s')){let s=d.createElement('style');s.id='s';s.textContent='.sk{display:flex;flex-direction:column;gap:8px;padding:10px}.sk .b{height:12px;background:linear-gradient(90deg,#e0e0e0 25%,#f0f0f0 50%,#e0e0e0 75%);background-size:200% 100%;animation:sh 1.5s infinite;border-radius:4px}@keyframes sh{0%{background-position:-200% 0}100%{background-position:200% 0}}.rt{display:inline-block;padding:8px 16px;background:#007bff;color:#fff;border-radius:4px;cursor:pointer;text-decoration:none;font-size:14px}.rt:hover{background:#0056b3}';h.appendChild(s)}let skT=d.createElement('template');skT.innerHTML='<div class="sk"><div class="b"></div><div class="b"></div><div class="b"></div></div>';let rtT=d.createElement('template');rtT.innerHTML='<span class="rt">Retry</span>';const phl=s=>{if(!s)return[];return s.replace(/^\[|\]$/g,'').split(',').map(x=>x.trim().replace(/^['"]|['"]$/g,'')).filter(Boolean)},inj=(r,t)=>{r.forEach(u=>{if(R.has(u))return;R.add(u);if(t==='css'){let l=d.createElement('link');l.rel='stylesheet';l.href=u;h.appendChild(l)}else if(t==='js'){let s=d.createElement('script');s.src=u;b.appendChild(s)}})},vis=el=>{let st=getComputedStyle(el);if(st.display==='none'||st.visibility==='hidden'||st.opacity==='0')return false;let r=el.getBoundingClientRect();return r.width>0&&r.height>0};let io=null;const gio=()=>{if(!io){io=new IntersectionObserver(es=>{es.forEach(e=>{let el=e.target,v=e.isIntersecting&&vis(el);if(v&&el.dataset.wasVisible!=='true')run(el);el.dataset.wasVisible=v?'true':'false'})},{threshold:0})}return io};const run=async el=>{if(el.dataset.running==='true')return;el.dataset.running='true';let post=el.hasAttribute('_post'),url=el.getAttribute('_post')||el.getAttribute('_get'),bd,hd={};if(post){let fid=el.getAttribute('_form'),js=el.getAttribute('_json');if(fid){let f=d.getElementById(fid);if(f)bd=new URLSearchParams(new FormData(f))}else if(js){bd=JSON.stringify(Object.fromEntries(js.split(',').map(n=>{let i=d.querySelector(`[name="${n}"]`);return[n,i?i.value:'']})));hd['Content-Type']='application/json'}}let ts=el.getAttribute('_target'),ct=C.get(el);if(!ct){ct=ts==='this'?el:d.querySelector(ts);if(ct)C.set(el,ct)}if(!ct){console.warn('Target not found:',ts);el.dataset.running='false';return}ct.replaceChildren(skT.content.cloneNode(true));try{let res=await fetch(url,{method:post?'POST':'GET',body:bd,headers:hd});let css=res.headers.get('x-css-required')||res.headers.get('x-css-requiered'),js=res.headers.get('x-js-required');if(css)inj(phl(css),'css');if(js)inj(phl(js),'js');ct.innerHTML=await res.text()}catch(e){console.error(e);let rt=rtT.content.firstElementChild.cloneNode(true);rt.addEventListener('click',ev=>{ev.preventDefault();ev.stopPropagation();run(el)});ct.replaceChildren(rt)}finally{el.dataset.running='false'}};const gte=el=>{let ev=T.get(el);if(ev)return ev;ev=new Set;let a=el.getAttribute('_trigger');if(a&&a.trim()){a.split(',').forEach(s=>{let e=s.trim().toLowerCase();if(V.has(e))ev.add(e)})}else{ev.add('click');if(el.getAttribute('_target')==='this')ev.add('load')}T.set(el,ev);return ev};const init=el=>{if(el.dataset.initialized==='true')return;el.dataset.initialized='true';let evs=gte(el);if(evs.has('load')||evs.has('domcontentloaded')||evs.has('ready'))run(el);if(evs.has('visible')||evs.has('intersect')){el.dataset.wasVisible='false';gio().observe(el)}};const delegated=['click','dblclick','mousedown','mouseup','mouseover','mouseout','mouseenter','mouseleave','mousemove','contextmenu','focus','blur','focusin','focusout','keydown','keyup','keypress','change','input','submit','reset','scroll','resize','wheel','touchstart','touchend','touchmove'];delegated.forEach(ev=>{let pass=['scroll','touchstart','touchmove','touchend','wheel'].includes(ev);d.addEventListener(ev,e=>{let t=e.target;if(!(t instanceof Element))return;let el=t.closest('[_get],[_post]');if(!el)return;let evs=gte(el);if(evs.has(ev)){if(ev==='click')e.preventDefault();run(el)}},pass?{passive:true}:false)});let mq=[],ms=false;const pm=()=>{ms=false;let nodes=mq;mq=[];nodes.forEach(n=>{if(n.nodeType!==1)return;if(n.matches('[_get],[_post]'))init(n);n.querySelectorAll('[_get],[_post]').forEach(init)})};new MutationObserver(ms=>{ms.forEach(m=>m.addedNodes.forEach(n=>{if(n.nodeType===1)mq.push(n)}));if(!ms){ms=true;Promise.resolve().then(pm)}}).observe(b,{childList:true,subtree:true});const initAll=()=>d.querySelectorAll('[_get],[_post]').forEach(init);if(d.readyState==='loading')d.addEventListener('DOMContentLoaded',initAll,{once:true});else initAll()})();
|
package/edge-server.min.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
class Context{constructor(r,e,x,p={},u=null){this.req=r;this.env=e;this.executionCtx=x;this.params=p;this.status=200;this.headers=new Headers;this._rawCookie=r.headers.get("Cookie")||"";this._cookies=null;this.url=u||new URL(r.url)}_ensureCookies(){if(this._cookies===null){let c={};if(this._rawCookie){for(const p of this._rawCookie.split(";")){const t=p.trim();if(!t)continue;const i=t.indexOf("=");if(i>0){const n=decodeURIComponent(t.slice(0,i)),v=decodeURIComponent(t.slice(i+1));c[n]=v}}}this._cookies=c}return this._cookies}getCookie(n){return this._ensureCookies()[n]??null}get query(){return this.url.searchParams}setCookie(n,v,o={}){let c=`${encodeURIComponent(n)}=${encodeURIComponent(v)}`;if(o.path)c+=`; Path=${o.path}`;if(o.domain)c+=`; Domain=${o.domain}`;if(o.maxAge!==undefined)c+=`; Max-Age=${o.maxAge}`;if(o.expires)c+=`; Expires=${o.expires.toUTCString()}`;if(o.secure)c+=`; Secure`;if(o.httpOnly)c+=`; HttpOnly`;if(o.sameSite)c+=`; SameSite=${o.sameSite}`;this.headers.append("Set-Cookie",c)}deleteCookie(n,o={}){this.setCookie(n,"",{...o,maxAge:0,expires:new Date(0)})}text(d,s=this.status,h={}){const r=this._buildHeaders(h);r.set("Content-Type","text/plain");return new Response(d,{status:s,headers:r})}json(d,s=this.status,h={}){const r=this._buildHeaders(h);r.set("Content-Type","application/json");return new Response(JSON.stringify(d),{status:s,headers:r})}html(d,s=this.status,h={}){const r=this._buildHeaders(h);r.set("Content-Type","text/html");return new Response(d,{status:s,headers:r})}_buildHeaders(h){if(Object.keys(h).length===0)return this.headers;const r=new Headers(this.headers);for(const k in h)if(Object.prototype.hasOwnProperty.call(h,k))r.set(k,h[k]);return r}}
|
|
2
|
-
const Fragment=Symbol("Fragment");
|
|
3
|
-
function jsx(t,p,...c){const n=p||{},f=c.flat(1/0);return{type:t,props:n,children:f,__isJSX:!0}}
|
|
4
|
-
function renderToString(n){if(n==null||typeof n=="boolean")return"";if(typeof n=="string"||typeof n=="number")return escapeHtml(String(n));if(Array.isArray(n)){const a=[];for(let i=0;i<n.length;i++)a.push(renderToString(n[i]));return a.join("")}if(!n.__isJSX)return escapeHtml(String(n));const{type:t,props:p,children:c}=n;if(t===Fragment){const a=[];for(let i=0;i<c.length;i++)a.push(renderToString(c[i]));return a.join("")}if(typeof t=="symbol")return"";if(typeof t=="function")return renderToString(t({...p,children:c}));const ap=[];for(const k in p){if(k==="children")continue;const v=p[k];if(v==null||v===!1)continue;if(k==="className")ap.push(` class="${escapeHtml(v)}"`);else if(k==="htmlFor")ap.push(` for="${escapeHtml(v)}"`);else if(k.startsWith("on")&&typeof v=="function")continue;else if(k==="style"&&typeof v=="object"){const s=Object.entries(v).map(([a,b])=>`${camelToKebab(a)}:${b}`).join(";");ap.push(` style="${escapeHtml(s)}"`)}else if(v===!0)ap.push(` ${k}`);else ap.push(` ${k}="${escapeHtml(String(v))}"`)}const at=ap.join(""),cp=[];for(let i=0;i<c.length;i++)cp.push(renderToString(c[i]));return `<${t}${at}>${cp.join("")}</${t}>`}
|
|
5
|
-
const HTML_ESCAPE_MAP={"&":"&","<":"<",">":">",'"':""","'":"'"};
|
|
6
|
-
function escapeHtml(s){return s.replace(/[&<>"']/g,c=>HTML_ESCAPE_MAP[c])}
|
|
7
|
-
function camelToKebab(s){return s.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}
|
|
8
|
-
class TrieNode{constructor(){this.children=new Map;this.paramChild=null;this.paramName=null;this.handler=null}}
|
|
9
|
-
class RouteTrie{constructor(){this.root=new TrieNode}add(p,h,o){const s=p.split("/").filter(Boolean);let n=this.root;for(const seg of s){if(seg.startsWith(":")){if(!n.paramChild){n.paramChild=new TrieNode;n.paramName=seg.slice(1)}n=n.paramChild}else{if(!n.children.has(seg))n.children.set(seg,new TrieNode);n=n.children.get(seg)}}n.handler={handler:h,options:o}}match(p){const s=p.split("/").filter(Boolean);let n=this.root;const pr={};for(const seg of s){if(n.children.has(seg))n=n.children.get(seg);else if(n.paramChild){pr[n.paramName]=seg;n=n.paramChild}else return null}return n.handler?{handler:n.handler.handler,options:n.handler.options,params:pr}:null}}
|
|
10
|
-
const HTTP_METHODS=["GET","POST","PUT","DELETE","PATCH","OPTIONS","HEAD"];
|
|
11
|
-
class Edge{constructor(){this.staticRoutes=new Map;for(const m of HTTP_METHODS)this.staticRoutes.set(m,new Map);this.dynamicTries={};for(const m of HTTP_METHODS)this.dynamicTries[m]=new RouteTrie;this.authKvBinding="AUTH_KV";this.rateLimitKvBinding="RATE_LIMIT_KV";this.defaults={cors:{origin:"*",methods:"GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD"}};this.scheduledHandler=null}_addRoute(m,p,o,h){if(typeof o=="boolean")o={auth:o};if(typeof o=="function"){h=o;o={}}if(typeof h!="function")throw new Error(`Handler for ${m} ${p} must be a function`);const mo={...this.defaults,...(o||{})};if(!p.includes(":"))this.staticRoutes.get(m).set(p,{handler:h,options:mo});else this.dynamicTries[m].add(p,h,mo)}get(p,o,h){this._addRoute("GET",p,o,h)}post(p,o,h){this._addRoute("POST",p,o,h)}put(p,o,h){this._addRoute("PUT",p,o,h)}delete(p,o,h){this._addRoute("DELETE",p,o,h)}patch(p,o,h){this._addRoute("PATCH",p,o,h)}options(p,o,h){this._addRoute("OPTIONS",p,o,h)}head(p,o,h){this._addRoute("HEAD",p,o,h)}scheduled(h){this.scheduledHandler=h}async _processAuth(c,f){if(!f)return!0;const t=c.getCookie("auth_token")||c.req.headers.get("Authorization")?.replace(/^Bearer\s+/i,"");if(!t)return!1;const kv=c.env[this.authKvBinding];if(!kv)return!1;const d=await kv.get(t);if(!d)return!1;if(typeof f=="object"){try{const pl=JSON.parse(d);if(f.role&&pl.role!==f.role)return!1;if(f.scopes){const us=pl.scopes||[];if(!f.scopes.every(s=>us.includes(s)))return!1}}catch{}}return!0}async _processRateLimit(c,f){if(!f)return!0;const o=f===!0?{}:f,{max=100,window=60}=o;const key=`rl:${c.req.headers.get("CF-Connecting-IP")||"unknown"}`;const kv=c.env[this.rateLimitKvBinding];if(!kv)return!0;let cnt=await kv.get(key,"json")||0;if(cnt>=max)return!1;cnt++;await kv.put(key,JSON.stringify(cnt),{expirationTtl:window});return!0}_processCors(c,f){if(!f)return;const o=typeof f=="object"?f:this.defaults.cors;c.headers.set("Access-Control-Allow-Origin",o.origin||"*");c.headers.set("Access-Control-Allow-Methods",o.methods||"GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD");c.headers.set("Access-Control-Allow-Headers",o.headers||"Content-Type, Authorization");c.headers.set("Access-Control-Max-Age","86400")}_processLog(c,f){if(f)console.log(`${c.req.method} ${c.req.url} - ${c.status}`)}_processCompress(req,res){const ae=req.headers.get("Accept-Encoding")||"";let enc=null;if(ae.includes("gzip"))enc="gzip";else if(ae.includes("deflate"))enc="deflate";else if(ae.includes("br"))enc="br";if(!enc||!res.body)return res;const st=res.body.pipeThrough(new CompressionStream(enc));const nh=new Headers(res.headers);nh.set("Content-Encoding",enc);nh.set("Vary","Accept-Encoding");return new Response(st,{status:res.status,statusText:res.statusText,headers:nh})}async _validate(c,f){if(!f)return!0;if(typeof f=="function"){try{return!!(await f(c))}catch{return!1}}return!0}async _cacheGet(req){const cache=caches.default;return await cache.match(req)||null}async _cachePut(req,res,f){if(!f)return;const o=f===!0?{}:f,{ttl=3600,staleWhileRevalidate=0}=o;const cache=caches.default;const clone=res.clone();clone.headers.set("Cache-Control",`max-age=${ttl}${staleWhileRevalidate>0?`, stale-while-revalidate=${staleWhileRevalidate}`:""}`);clone.headers.delete("Set-Cookie");await cache.put(req,clone)}async fetch(req,env,ctx){const url=new URL(req.url),path=url.pathname,method=req.method;const mm=this.staticRoutes.get(method);if(mm){const sr=mm.get(path);if(sr)return this._handleRoute(sr.handler,sr.options,req,env,ctx,{},url)}const trie=this.dynamicTries[method];if(trie){const m=trie.match(path);if(m)return this._handleRoute(m.handler,m.options,req,env,ctx,m.params,url)}return new Response("Not Found",{status:404})}async _handleRoute(h,o,req,env,ctx,p,url){const c=new Context(req,env,ctx,p,url);if(!(await this._validate(c,o.validate)))return c.text("Validation failed",400);if(!(await this._processAuth(c,o.auth)))return c.text("Unauthorized",401);if(!(await this._processRateLimit(c,o.rateLimit)))return c.text("Too Many Requests",429);let cached=null;if(o.cache&&req.method==="GET"){cached=await this._cacheGet(req);if(cached){this._processCors(c,o.cors);this._processLog(c,o.log);return cached}}this._processCors(c,o.cors);let res;try{const r=await h(c);if(r&&r.__isJSX)res=c.html(renderToString(r));else res=r instanceof Response?r:c.text("OK");c.status=res.status}catch(e){console.error(e);res=c.text("Internal Server Error",500);c.status=500}if(o.cache&&req.method==="GET"&&res.status===200)c.executionCtx.waitUntil(this._cachePut(req,res.clone(),o.cache));if(o.compress)res=this._processCompress(req,res);this._processLog(c,o.log);return res}}
|
|
12
|
-
export{Fragment,jsx,renderToString,Edge};
|
package/server.min.d.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
export const Fragment: unique symbol;
|
|
2
|
-
|
|
3
|
-
export interface JSXNode {
|
|
4
|
-
type: any;
|
|
5
|
-
props: Record<string, any>;
|
|
6
|
-
children: any[];
|
|
7
|
-
__isJSX: true;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function jsx(
|
|
11
|
-
type: any,
|
|
12
|
-
props?: Record<string, any> | null,
|
|
13
|
-
...children: any[]
|
|
14
|
-
): JSXNode;
|
|
15
|
-
|
|
16
|
-
export function renderToString(node: any): string;
|
|
17
|
-
|
|
18
|
-
export interface RouteOptions {
|
|
19
|
-
auth?: boolean | { role?: string; scopes?: string[] };
|
|
20
|
-
rateLimit?: boolean | { max?: number; window?: number };
|
|
21
|
-
cors?: boolean | { origin?: string; methods?: string; headers?: string };
|
|
22
|
-
validate?: (ctx: Context) => boolean | Promise<boolean>;
|
|
23
|
-
log?: boolean;
|
|
24
|
-
cache?: boolean | { ttl?: number; staleWhileRevalidate?: number };
|
|
25
|
-
compress?: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export class Context {
|
|
29
|
-
req: Request;
|
|
30
|
-
env: any;
|
|
31
|
-
executionCtx: ExecutionContext;
|
|
32
|
-
params: Record<string, string>;
|
|
33
|
-
status: number;
|
|
34
|
-
headers: Headers;
|
|
35
|
-
|
|
36
|
-
constructor(
|
|
37
|
-
request: Request,
|
|
38
|
-
env: any,
|
|
39
|
-
executionCtx: ExecutionContext,
|
|
40
|
-
params?: Record<string, string>,
|
|
41
|
-
parsedUrl?: URL
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
getCookie(name: string): string | null;
|
|
45
|
-
get query(): URLSearchParams;
|
|
46
|
-
setCookie(name: string, value: string, options?: Record<string, any>): void;
|
|
47
|
-
deleteCookie(name: string, options?: Record<string, any>): void;
|
|
48
|
-
text(data: string, status?: number, headers?: Record<string, string>): Response;
|
|
49
|
-
json(data: any, status?: number, headers?: Record<string, string>): Response;
|
|
50
|
-
html(data: string, status?: number, headers?: Record<string, string>): Response;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export class Edge {
|
|
54
|
-
constructor();
|
|
55
|
-
authKvBinding: string;
|
|
56
|
-
rateLimitKvBinding: string;
|
|
57
|
-
defaults: {
|
|
58
|
-
cors: { origin: string; methods: string };
|
|
59
|
-
};
|
|
60
|
-
scheduledHandler: ((...args: any[]) => void) | null;
|
|
61
|
-
|
|
62
|
-
get(path: string, handler: (ctx: Context) => any): void;
|
|
63
|
-
get(path: string, options: RouteOptions, handler: (ctx: Context) => any): void;
|
|
64
|
-
post(path: string, handler: (ctx: Context) => any): void;
|
|
65
|
-
post(path: string, options: RouteOptions, handler: (ctx: Context) => any): void;
|
|
66
|
-
put(path: string, handler: (ctx: Context) => any): void;
|
|
67
|
-
put(path: string, options: RouteOptions, handler: (ctx: Context) => any): void;
|
|
68
|
-
delete(path: string, handler: (ctx: Context) => any): void;
|
|
69
|
-
delete(path: string, options: RouteOptions, handler: (ctx: Context) => any): void;
|
|
70
|
-
patch(path: string, handler: (ctx: Context) => any): void;
|
|
71
|
-
patch(path: string, options: RouteOptions, handler: (ctx: Context) => any): void;
|
|
72
|
-
options(path: string, handler: (ctx: Context) => any): void;
|
|
73
|
-
options(path: string, options: RouteOptions, handler: (ctx: Context) => any): void;
|
|
74
|
-
head(path: string, handler: (ctx: Context) => any): void;
|
|
75
|
-
head(path: string, options: RouteOptions, handler: (ctx: Context) => any): void;
|
|
76
|
-
scheduled(handler: (...args: any[]) => void): void;
|
|
77
|
-
|
|
78
|
-
fetch(
|
|
79
|
-
request: Request,
|
|
80
|
-
env: any,
|
|
81
|
-
executionCtx: ExecutionContext
|
|
82
|
-
): Promise<Response>;
|
|
83
|
-
}
|