@kuratchi/js 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +770 -691
- package/dist/compiler/index.js +1125 -857
- package/dist/compiler/template.js +74 -28
- package/dist/runtime/types.d.ts +0 -14
- package/package.json +1 -1
package/dist/compiler/index.js
CHANGED
|
@@ -252,385 +252,385 @@ export function compile(options) {
|
|
|
252
252
|
// - server actions bound via onX={serverAction(...)} -> [data-action][data-action-event]
|
|
253
253
|
// - declarative confirm="..."
|
|
254
254
|
// - declarative checkbox groups: data-select-all / data-select-item
|
|
255
|
-
const bridgeSource = `(function(){
|
|
256
|
-
function by(sel, root){ return Array.prototype.slice.call((root || document).querySelectorAll(sel)); }
|
|
257
|
-
var __refreshSeq = Object.create(null);
|
|
258
|
-
function syncGroup(group){
|
|
259
|
-
var items = by('[data-select-item]').filter(function(el){ return el.getAttribute('data-select-item') === group; });
|
|
260
|
-
var masters = by('[data-select-all]').filter(function(el){ return el.getAttribute('data-select-all') === group; });
|
|
261
|
-
if(!items.length || !masters.length) return;
|
|
262
|
-
var all = items.every(function(i){ return !!i.checked; });
|
|
263
|
-
var any = items.some(function(i){ return !!i.checked; });
|
|
264
|
-
masters.forEach(function(m){ m.checked = all; m.indeterminate = any && !all; });
|
|
265
|
-
}
|
|
266
|
-
function inferQueryKey(getName, argsRaw){
|
|
267
|
-
if(!getName) return '';
|
|
268
|
-
return 'query:' + String(getName) + '|' + (argsRaw || '[]');
|
|
269
|
-
}
|
|
270
|
-
function blockKey(el){
|
|
271
|
-
if(!el || !el.getAttribute) return '';
|
|
272
|
-
var explicit = el.getAttribute('data-key');
|
|
273
|
-
if(explicit) return 'key:' + explicit;
|
|
274
|
-
var inferred = inferQueryKey(el.getAttribute('data-get'), el.getAttribute('data-get-args'));
|
|
275
|
-
if(inferred) return inferred;
|
|
276
|
-
var asName = el.getAttribute('data-as');
|
|
277
|
-
if(asName) return 'as:' + asName;
|
|
278
|
-
return '';
|
|
279
|
-
}
|
|
280
|
-
function escHtml(v){
|
|
281
|
-
return String(v || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
282
|
-
}
|
|
283
|
-
function setBlocksLoading(blocks){
|
|
284
|
-
blocks.forEach(function(el){
|
|
285
|
-
el.setAttribute('aria-busy','true');
|
|
286
|
-
el.setAttribute('data-kuratchi-loading','1');
|
|
287
|
-
var text = el.getAttribute('data-loading-text');
|
|
288
|
-
if(text && !el.hasAttribute('data-as')){ el.innerHTML = '<p>' + escHtml(text) + '</p>'; return; }
|
|
289
|
-
el.style.opacity = '0.6';
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
function clearBlocksLoading(blocks){
|
|
293
|
-
blocks.forEach(function(el){
|
|
294
|
-
el.removeAttribute('aria-busy');
|
|
295
|
-
el.removeAttribute('data-kuratchi-loading');
|
|
296
|
-
el.style.opacity = '';
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
function replaceBlocksWithKey(key){
|
|
300
|
-
if(!key || typeof DOMParser === 'undefined'){ location.reload(); return Promise.resolve(); }
|
|
301
|
-
var oldBlocks = by('[data-get]').filter(function(el){ return blockKey(el) === key; });
|
|
302
|
-
if(!oldBlocks.length){ location.reload(); return Promise.resolve(); }
|
|
303
|
-
var first = oldBlocks[0];
|
|
304
|
-
var qFn = first ? (first.getAttribute('data-get') || '') : '';
|
|
305
|
-
var qArgs = first ? String(first.getAttribute('data-get-args') || '[]') : '[]';
|
|
306
|
-
var seq = (__refreshSeq[key] || 0) + 1;
|
|
307
|
-
__refreshSeq[key] = seq;
|
|
308
|
-
setBlocksLoading(oldBlocks);
|
|
309
|
-
var headers = { 'x-kuratchi-refresh': '1' };
|
|
310
|
-
if(qFn){ headers['x-kuratchi-query-fn'] = String(qFn); headers['x-kuratchi-query-args'] = qArgs; }
|
|
311
|
-
return fetch(location.pathname + location.search, { headers: headers })
|
|
312
|
-
.then(function(r){ if(!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
|
|
313
|
-
.then(function(html){
|
|
314
|
-
if(__refreshSeq[key] !== seq) return;
|
|
315
|
-
var doc = new DOMParser().parseFromString(html, 'text/html');
|
|
316
|
-
var newBlocks = by('[data-get]', doc).filter(function(el){ return blockKey(el) === key; });
|
|
317
|
-
if(!oldBlocks.length || !newBlocks.length){ location.reload(); return; }
|
|
318
|
-
for(var i=0;i<oldBlocks.length;i++){ if(newBlocks[i]) oldBlocks[i].outerHTML = newBlocks[i].outerHTML; }
|
|
319
|
-
by('[data-select-all]').forEach(function(m){ var g=m.getAttribute('data-select-all'); if(g) syncGroup(g); });
|
|
320
|
-
})
|
|
321
|
-
.catch(function(){
|
|
322
|
-
if(__refreshSeq[key] === seq) clearBlocksLoading(oldBlocks);
|
|
323
|
-
location.reload();
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
function replaceBlocksByDescriptor(fnName, argsRaw){
|
|
327
|
-
if(!fnName || typeof DOMParser === 'undefined'){ location.reload(); return Promise.resolve(); }
|
|
328
|
-
var normalizedArgs = String(argsRaw || '[]');
|
|
329
|
-
var oldBlocks = by('[data-get]').filter(function(el){
|
|
330
|
-
return (el.getAttribute('data-get') || '') === String(fnName) &&
|
|
331
|
-
String(el.getAttribute('data-get-args') || '[]') === normalizedArgs;
|
|
332
|
-
});
|
|
333
|
-
if(!oldBlocks.length){ location.reload(); return Promise.resolve(); }
|
|
334
|
-
var key = 'fn:' + String(fnName) + '|' + normalizedArgs;
|
|
335
|
-
var seq = (__refreshSeq[key] || 0) + 1;
|
|
336
|
-
__refreshSeq[key] = seq;
|
|
337
|
-
setBlocksLoading(oldBlocks);
|
|
338
|
-
return fetch(location.pathname + location.search, {
|
|
339
|
-
headers: {
|
|
340
|
-
'x-kuratchi-refresh': '1',
|
|
341
|
-
'x-kuratchi-query-fn': String(fnName),
|
|
342
|
-
'x-kuratchi-query-args': normalizedArgs,
|
|
343
|
-
}
|
|
344
|
-
})
|
|
345
|
-
.then(function(r){ if(!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
|
|
346
|
-
.then(function(html){
|
|
347
|
-
if(__refreshSeq[key] !== seq) return;
|
|
348
|
-
var doc = new DOMParser().parseFromString(html, 'text/html');
|
|
349
|
-
var newBlocks = by('[data-get]', doc).filter(function(el){
|
|
350
|
-
return (el.getAttribute('data-get') || '') === String(fnName) &&
|
|
351
|
-
String(el.getAttribute('data-get-args') || '[]') === normalizedArgs;
|
|
352
|
-
});
|
|
353
|
-
if(!newBlocks.length){ location.reload(); return; }
|
|
354
|
-
for(var i=0;i<oldBlocks.length;i++){ if(newBlocks[i]) oldBlocks[i].outerHTML = newBlocks[i].outerHTML; }
|
|
355
|
-
by('[data-select-all]').forEach(function(m){ var g=m.getAttribute('data-select-all'); if(g) syncGroup(g); });
|
|
356
|
-
})
|
|
357
|
-
.catch(function(){
|
|
358
|
-
if(__refreshSeq[key] === seq) clearBlocksLoading(oldBlocks);
|
|
359
|
-
location.reload();
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
function refreshByDescriptor(fnName, argsRaw){
|
|
363
|
-
if(!fnName) { location.reload(); return Promise.resolve(); }
|
|
364
|
-
return replaceBlocksByDescriptor(fnName, argsRaw || '[]');
|
|
365
|
-
}
|
|
366
|
-
function refreshNearest(el){
|
|
367
|
-
var host = el && el.closest ? el.closest('[data-get]') : null;
|
|
368
|
-
if(!host){ location.reload(); return Promise.resolve(); }
|
|
369
|
-
return replaceBlocksWithKey(blockKey(host));
|
|
370
|
-
}
|
|
371
|
-
function refreshTargets(raw){
|
|
372
|
-
if(!raw){ location.reload(); return Promise.resolve(); }
|
|
373
|
-
var keys = String(raw).split(',').map(function(v){ return v.trim(); }).filter(Boolean);
|
|
374
|
-
if(!keys.length){ location.reload(); return Promise.resolve(); }
|
|
375
|
-
return Promise.all(keys.map(function(k){ return replaceBlocksWithKey('key:' + k); })).then(function(){});
|
|
376
|
-
}
|
|
377
|
-
function act(e){
|
|
378
|
-
if(e.type === 'click'){
|
|
379
|
-
var g = e.target && e.target.closest ? e.target.closest('[data-get]') : null;
|
|
380
|
-
if(g && !g.hasAttribute('data-as') && !g.hasAttribute('data-action')){
|
|
381
|
-
var getUrl = g.getAttribute('data-get');
|
|
382
|
-
if(getUrl){
|
|
383
|
-
if(/^[a-z][a-z0-9+\-.]*:/i.test(getUrl) && !/^https?:/i.test(getUrl)) return;
|
|
384
|
-
e.preventDefault();
|
|
385
|
-
location.assign(getUrl);
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
var r = e.target && e.target.closest ? e.target.closest('[data-refresh]') : null;
|
|
390
|
-
if(r && !r.hasAttribute('data-action')){
|
|
391
|
-
e.preventDefault();
|
|
392
|
-
var rf = r.getAttribute('data-refresh');
|
|
393
|
-
var ra = r.getAttribute('data-refresh-args');
|
|
394
|
-
if(ra !== null){ refreshByDescriptor(rf, ra || '[]'); return; }
|
|
395
|
-
if(rf && rf.trim()){ refreshTargets(rf); return; }
|
|
396
|
-
refreshNearest(r);
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
var sel = '[data-action][data-action-event="' + e.type + '"]';
|
|
401
|
-
var b = e.target && e.target.closest ? e.target.closest(sel) : null;
|
|
402
|
-
if(!b) return;
|
|
403
|
-
e.preventDefault();
|
|
404
|
-
var fd = new FormData();
|
|
405
|
-
fd.append('_action', b.getAttribute('data-action') || '');
|
|
406
|
-
fd.append('_args', b.getAttribute('data-args') || '[]');
|
|
407
|
-
var m = b.getAttribute('data-action-method');
|
|
408
|
-
if(m) fd.append('_method', String(m).toUpperCase());
|
|
409
|
-
fetch(location.pathname, { method: 'POST', body: fd })
|
|
410
|
-
.then(function(r){
|
|
411
|
-
if(!r.ok){
|
|
412
|
-
return r.json().then(function(j){ throw new Error((j && j.error) || ('HTTP ' + r.status)); }).catch(function(){ throw new Error('HTTP ' + r.status); });
|
|
413
|
-
}
|
|
414
|
-
return r.json();
|
|
415
|
-
})
|
|
416
|
-
.then(function(j){
|
|
417
|
-
if(j && j.redirectTo){ location.assign(j.redirectTo); return; }
|
|
418
|
-
if(!b.hasAttribute('data-refresh')) return;
|
|
419
|
-
var refreshFn = b.getAttribute('data-refresh');
|
|
420
|
-
var refreshArgs = b.getAttribute('data-refresh-args');
|
|
421
|
-
if(refreshArgs !== null){ return refreshByDescriptor(refreshFn, refreshArgs || '[]'); }
|
|
422
|
-
if(refreshFn && refreshFn.trim()){ return refreshTargets(refreshFn); }
|
|
423
|
-
return refreshNearest(b);
|
|
424
|
-
})
|
|
425
|
-
.catch(function(err){ console.error('[kuratchi] client action error:', err); });
|
|
426
|
-
}
|
|
427
|
-
['click','change','input','focus','blur'].forEach(function(ev){ document.addEventListener(ev, act, true); });
|
|
428
|
-
function autoLoadQueries(){
|
|
429
|
-
var seen = Object.create(null);
|
|
430
|
-
by('[data-get][data-as]').forEach(function(el){
|
|
431
|
-
var fn = el.getAttribute('data-get');
|
|
432
|
-
if(!fn) return;
|
|
433
|
-
var args = String(el.getAttribute('data-get-args') || '[]');
|
|
434
|
-
var key = String(fn) + '|' + args;
|
|
435
|
-
if(seen[key]) return;
|
|
436
|
-
seen[key] = true;
|
|
437
|
-
replaceBlocksByDescriptor(fn, args);
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
if(document.readyState === 'loading'){
|
|
441
|
-
document.addEventListener('DOMContentLoaded', autoLoadQueries, { once: true });
|
|
442
|
-
} else {
|
|
443
|
-
autoLoadQueries();
|
|
444
|
-
}
|
|
445
|
-
document.addEventListener('click', function(e){
|
|
446
|
-
var b = e.target && e.target.closest ? e.target.closest('[command="fill-dialog"]') : null;
|
|
447
|
-
if(!b) return;
|
|
448
|
-
var targetId = b.getAttribute('commandfor');
|
|
449
|
-
if(!targetId) return;
|
|
450
|
-
var dialog = document.getElementById(targetId);
|
|
451
|
-
if(!dialog) return;
|
|
452
|
-
var raw = b.getAttribute('data-dialog-data');
|
|
453
|
-
if(!raw) return;
|
|
454
|
-
var data;
|
|
455
|
-
try { data = JSON.parse(raw); } catch(_err) { return; }
|
|
456
|
-
Object.keys(data).forEach(function(k){
|
|
457
|
-
var inp = dialog.querySelector('[name="col_' + k + '"]');
|
|
458
|
-
if(inp){
|
|
459
|
-
inp.value = data[k] === null || data[k] === undefined ? '' : String(data[k]);
|
|
460
|
-
inp.placeholder = data[k] === null || data[k] === undefined ? 'NULL' : '';
|
|
461
|
-
}
|
|
462
|
-
var hidden = dialog.querySelector('#dialog-field-' + k);
|
|
463
|
-
if(hidden){
|
|
464
|
-
hidden.value = data[k] === null || data[k] === undefined ? '' : String(data[k]);
|
|
465
|
-
}
|
|
466
|
-
});
|
|
467
|
-
var rowidInp = dialog.querySelector('[name="rowid"]');
|
|
468
|
-
if(rowidInp && data.rowid !== undefined) rowidInp.value = String(data.rowid);
|
|
469
|
-
if(typeof dialog.showModal === 'function') dialog.showModal();
|
|
470
|
-
}, true);
|
|
471
|
-
(function initPoll(){
|
|
472
|
-
var prev = {};
|
|
473
|
-
function bindPollEl(el){
|
|
474
|
-
if(!el || !el.getAttribute) return;
|
|
475
|
-
if(el.getAttribute('data-kuratchi-poll-bound') === '1') return;
|
|
476
|
-
var fn = el.getAttribute('data-poll');
|
|
477
|
-
if(!fn) return;
|
|
478
|
-
el.setAttribute('data-kuratchi-poll-bound', '1');
|
|
479
|
-
var args = el.getAttribute('data-poll-args') || '[]';
|
|
480
|
-
var iv = parseInt(el.getAttribute('data-interval') || '', 10) || 3000;
|
|
481
|
-
var key = String(fn) + args;
|
|
482
|
-
if(!(key in prev)) prev[key] = null;
|
|
483
|
-
(function tick(){
|
|
484
|
-
setTimeout(function(){
|
|
485
|
-
fetch(location.pathname + '?_rpc=' + encodeURIComponent(String(fn)) + '&_args=' + encodeURIComponent(args), { headers: { 'x-kuratchi-rpc': '1' } })
|
|
486
|
-
.then(function(r){ return r.json(); })
|
|
487
|
-
.then(function(j){
|
|
488
|
-
if(j && j.ok){
|
|
489
|
-
var s = JSON.stringify(j.data);
|
|
490
|
-
if(prev[key] !== null && prev[key] !== s){ location.reload(); return; }
|
|
491
|
-
prev[key] = s;
|
|
492
|
-
}
|
|
493
|
-
tick();
|
|
494
|
-
})
|
|
495
|
-
.catch(function(){ tick(); });
|
|
496
|
-
}, iv);
|
|
497
|
-
})();
|
|
498
|
-
}
|
|
499
|
-
function scan(){
|
|
500
|
-
by('[data-poll]').forEach(bindPollEl);
|
|
501
|
-
}
|
|
502
|
-
scan();
|
|
503
|
-
setInterval(scan, 500);
|
|
504
|
-
})();
|
|
505
|
-
function confirmClick(e){
|
|
506
|
-
var el = e.target && e.target.closest ? e.target.closest('[confirm]') : null;
|
|
507
|
-
if(!el) return;
|
|
508
|
-
var msg = el.getAttribute('confirm');
|
|
509
|
-
if(!msg) return;
|
|
510
|
-
if(!window.confirm(msg)){ e.preventDefault(); e.stopPropagation(); }
|
|
511
|
-
}
|
|
512
|
-
document.addEventListener('click', confirmClick, true);
|
|
513
|
-
document.addEventListener('submit', function(e){
|
|
514
|
-
var f = e.target && e.target.matches && e.target.matches('form[confirm]') ? e.target : null;
|
|
515
|
-
if(!f) return;
|
|
516
|
-
var msg = f.getAttribute('confirm');
|
|
517
|
-
if(!msg) return;
|
|
518
|
-
if(!window.confirm(msg)){ e.preventDefault(); e.stopPropagation(); }
|
|
519
|
-
}, true);
|
|
520
|
-
document.addEventListener('submit', function(e){
|
|
521
|
-
if(e.defaultPrevented) return;
|
|
522
|
-
var f = e.target;
|
|
523
|
-
if(!f || !f.querySelector) return;
|
|
524
|
-
var aInput = f.querySelector('input[name="_action"]');
|
|
525
|
-
if(!aInput) return;
|
|
526
|
-
var aName = aInput.value;
|
|
527
|
-
if(!aName) return;
|
|
528
|
-
f.setAttribute('data-action-loading', aName);
|
|
529
|
-
Array.prototype.slice.call(f.querySelectorAll('button[type="submit"],button:not([type="button"]):not([type="reset"])')).forEach(function(b){ b.disabled = true; });
|
|
530
|
-
}, true);
|
|
531
|
-
document.addEventListener('change', function(e){
|
|
532
|
-
var t = e.target;
|
|
533
|
-
if(!t || !t.getAttribute) return;
|
|
534
|
-
var gAll = t.getAttribute('data-select-all');
|
|
535
|
-
if(gAll){
|
|
536
|
-
by('[data-select-item]').filter(function(i){ return i.getAttribute('data-select-item') === gAll; }).forEach(function(i){ i.checked = !!t.checked; });
|
|
537
|
-
syncGroup(gAll);
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
var gItem = t.getAttribute('data-select-item');
|
|
541
|
-
if(gItem) syncGroup(gItem);
|
|
542
|
-
}, true);
|
|
543
|
-
by('[data-select-all]').forEach(function(m){ var g = m.getAttribute('data-select-all'); if(g) syncGroup(g); });
|
|
255
|
+
const bridgeSource = `(function(){
|
|
256
|
+
function by(sel, root){ return Array.prototype.slice.call((root || document).querySelectorAll(sel)); }
|
|
257
|
+
var __refreshSeq = Object.create(null);
|
|
258
|
+
function syncGroup(group){
|
|
259
|
+
var items = by('[data-select-item]').filter(function(el){ return el.getAttribute('data-select-item') === group; });
|
|
260
|
+
var masters = by('[data-select-all]').filter(function(el){ return el.getAttribute('data-select-all') === group; });
|
|
261
|
+
if(!items.length || !masters.length) return;
|
|
262
|
+
var all = items.every(function(i){ return !!i.checked; });
|
|
263
|
+
var any = items.some(function(i){ return !!i.checked; });
|
|
264
|
+
masters.forEach(function(m){ m.checked = all; m.indeterminate = any && !all; });
|
|
265
|
+
}
|
|
266
|
+
function inferQueryKey(getName, argsRaw){
|
|
267
|
+
if(!getName) return '';
|
|
268
|
+
return 'query:' + String(getName) + '|' + (argsRaw || '[]');
|
|
269
|
+
}
|
|
270
|
+
function blockKey(el){
|
|
271
|
+
if(!el || !el.getAttribute) return '';
|
|
272
|
+
var explicit = el.getAttribute('data-key');
|
|
273
|
+
if(explicit) return 'key:' + explicit;
|
|
274
|
+
var inferred = inferQueryKey(el.getAttribute('data-get'), el.getAttribute('data-get-args'));
|
|
275
|
+
if(inferred) return inferred;
|
|
276
|
+
var asName = el.getAttribute('data-as');
|
|
277
|
+
if(asName) return 'as:' + asName;
|
|
278
|
+
return '';
|
|
279
|
+
}
|
|
280
|
+
function escHtml(v){
|
|
281
|
+
return String(v || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
282
|
+
}
|
|
283
|
+
function setBlocksLoading(blocks){
|
|
284
|
+
blocks.forEach(function(el){
|
|
285
|
+
el.setAttribute('aria-busy','true');
|
|
286
|
+
el.setAttribute('data-kuratchi-loading','1');
|
|
287
|
+
var text = el.getAttribute('data-loading-text');
|
|
288
|
+
if(text && !el.hasAttribute('data-as')){ el.innerHTML = '<p>' + escHtml(text) + '</p>'; return; }
|
|
289
|
+
el.style.opacity = '0.6';
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
function clearBlocksLoading(blocks){
|
|
293
|
+
blocks.forEach(function(el){
|
|
294
|
+
el.removeAttribute('aria-busy');
|
|
295
|
+
el.removeAttribute('data-kuratchi-loading');
|
|
296
|
+
el.style.opacity = '';
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function replaceBlocksWithKey(key){
|
|
300
|
+
if(!key || typeof DOMParser === 'undefined'){ location.reload(); return Promise.resolve(); }
|
|
301
|
+
var oldBlocks = by('[data-get]').filter(function(el){ return blockKey(el) === key; });
|
|
302
|
+
if(!oldBlocks.length){ location.reload(); return Promise.resolve(); }
|
|
303
|
+
var first = oldBlocks[0];
|
|
304
|
+
var qFn = first ? (first.getAttribute('data-get') || '') : '';
|
|
305
|
+
var qArgs = first ? String(first.getAttribute('data-get-args') || '[]') : '[]';
|
|
306
|
+
var seq = (__refreshSeq[key] || 0) + 1;
|
|
307
|
+
__refreshSeq[key] = seq;
|
|
308
|
+
setBlocksLoading(oldBlocks);
|
|
309
|
+
var headers = { 'x-kuratchi-refresh': '1' };
|
|
310
|
+
if(qFn){ headers['x-kuratchi-query-fn'] = String(qFn); headers['x-kuratchi-query-args'] = qArgs; }
|
|
311
|
+
return fetch(location.pathname + location.search, { headers: headers })
|
|
312
|
+
.then(function(r){ if(!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
|
|
313
|
+
.then(function(html){
|
|
314
|
+
if(__refreshSeq[key] !== seq) return;
|
|
315
|
+
var doc = new DOMParser().parseFromString(html, 'text/html');
|
|
316
|
+
var newBlocks = by('[data-get]', doc).filter(function(el){ return blockKey(el) === key; });
|
|
317
|
+
if(!oldBlocks.length || !newBlocks.length){ location.reload(); return; }
|
|
318
|
+
for(var i=0;i<oldBlocks.length;i++){ if(newBlocks[i]) oldBlocks[i].outerHTML = newBlocks[i].outerHTML; }
|
|
319
|
+
by('[data-select-all]').forEach(function(m){ var g=m.getAttribute('data-select-all'); if(g) syncGroup(g); });
|
|
320
|
+
})
|
|
321
|
+
.catch(function(){
|
|
322
|
+
if(__refreshSeq[key] === seq) clearBlocksLoading(oldBlocks);
|
|
323
|
+
location.reload();
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function replaceBlocksByDescriptor(fnName, argsRaw){
|
|
327
|
+
if(!fnName || typeof DOMParser === 'undefined'){ location.reload(); return Promise.resolve(); }
|
|
328
|
+
var normalizedArgs = String(argsRaw || '[]');
|
|
329
|
+
var oldBlocks = by('[data-get]').filter(function(el){
|
|
330
|
+
return (el.getAttribute('data-get') || '') === String(fnName) &&
|
|
331
|
+
String(el.getAttribute('data-get-args') || '[]') === normalizedArgs;
|
|
332
|
+
});
|
|
333
|
+
if(!oldBlocks.length){ location.reload(); return Promise.resolve(); }
|
|
334
|
+
var key = 'fn:' + String(fnName) + '|' + normalizedArgs;
|
|
335
|
+
var seq = (__refreshSeq[key] || 0) + 1;
|
|
336
|
+
__refreshSeq[key] = seq;
|
|
337
|
+
setBlocksLoading(oldBlocks);
|
|
338
|
+
return fetch(location.pathname + location.search, {
|
|
339
|
+
headers: {
|
|
340
|
+
'x-kuratchi-refresh': '1',
|
|
341
|
+
'x-kuratchi-query-fn': String(fnName),
|
|
342
|
+
'x-kuratchi-query-args': normalizedArgs,
|
|
343
|
+
}
|
|
344
|
+
})
|
|
345
|
+
.then(function(r){ if(!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
|
|
346
|
+
.then(function(html){
|
|
347
|
+
if(__refreshSeq[key] !== seq) return;
|
|
348
|
+
var doc = new DOMParser().parseFromString(html, 'text/html');
|
|
349
|
+
var newBlocks = by('[data-get]', doc).filter(function(el){
|
|
350
|
+
return (el.getAttribute('data-get') || '') === String(fnName) &&
|
|
351
|
+
String(el.getAttribute('data-get-args') || '[]') === normalizedArgs;
|
|
352
|
+
});
|
|
353
|
+
if(!newBlocks.length){ location.reload(); return; }
|
|
354
|
+
for(var i=0;i<oldBlocks.length;i++){ if(newBlocks[i]) oldBlocks[i].outerHTML = newBlocks[i].outerHTML; }
|
|
355
|
+
by('[data-select-all]').forEach(function(m){ var g=m.getAttribute('data-select-all'); if(g) syncGroup(g); });
|
|
356
|
+
})
|
|
357
|
+
.catch(function(){
|
|
358
|
+
if(__refreshSeq[key] === seq) clearBlocksLoading(oldBlocks);
|
|
359
|
+
location.reload();
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
function refreshByDescriptor(fnName, argsRaw){
|
|
363
|
+
if(!fnName) { location.reload(); return Promise.resolve(); }
|
|
364
|
+
return replaceBlocksByDescriptor(fnName, argsRaw || '[]');
|
|
365
|
+
}
|
|
366
|
+
function refreshNearest(el){
|
|
367
|
+
var host = el && el.closest ? el.closest('[data-get]') : null;
|
|
368
|
+
if(!host){ location.reload(); return Promise.resolve(); }
|
|
369
|
+
return replaceBlocksWithKey(blockKey(host));
|
|
370
|
+
}
|
|
371
|
+
function refreshTargets(raw){
|
|
372
|
+
if(!raw){ location.reload(); return Promise.resolve(); }
|
|
373
|
+
var keys = String(raw).split(',').map(function(v){ return v.trim(); }).filter(Boolean);
|
|
374
|
+
if(!keys.length){ location.reload(); return Promise.resolve(); }
|
|
375
|
+
return Promise.all(keys.map(function(k){ return replaceBlocksWithKey('key:' + k); })).then(function(){});
|
|
376
|
+
}
|
|
377
|
+
function act(e){
|
|
378
|
+
if(e.type === 'click'){
|
|
379
|
+
var g = e.target && e.target.closest ? e.target.closest('[data-get]') : null;
|
|
380
|
+
if(g && !g.hasAttribute('data-as') && !g.hasAttribute('data-action')){
|
|
381
|
+
var getUrl = g.getAttribute('data-get');
|
|
382
|
+
if(getUrl){
|
|
383
|
+
if(/^[a-z][a-z0-9+\-.]*:/i.test(getUrl) && !/^https?:/i.test(getUrl)) return;
|
|
384
|
+
e.preventDefault();
|
|
385
|
+
location.assign(getUrl);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
var r = e.target && e.target.closest ? e.target.closest('[data-refresh]') : null;
|
|
390
|
+
if(r && !r.hasAttribute('data-action')){
|
|
391
|
+
e.preventDefault();
|
|
392
|
+
var rf = r.getAttribute('data-refresh');
|
|
393
|
+
var ra = r.getAttribute('data-refresh-args');
|
|
394
|
+
if(ra !== null){ refreshByDescriptor(rf, ra || '[]'); return; }
|
|
395
|
+
if(rf && rf.trim()){ refreshTargets(rf); return; }
|
|
396
|
+
refreshNearest(r);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
var sel = '[data-action][data-action-event="' + e.type + '"]';
|
|
401
|
+
var b = e.target && e.target.closest ? e.target.closest(sel) : null;
|
|
402
|
+
if(!b) return;
|
|
403
|
+
e.preventDefault();
|
|
404
|
+
var fd = new FormData();
|
|
405
|
+
fd.append('_action', b.getAttribute('data-action') || '');
|
|
406
|
+
fd.append('_args', b.getAttribute('data-args') || '[]');
|
|
407
|
+
var m = b.getAttribute('data-action-method');
|
|
408
|
+
if(m) fd.append('_method', String(m).toUpperCase());
|
|
409
|
+
fetch(location.pathname, { method: 'POST', body: fd })
|
|
410
|
+
.then(function(r){
|
|
411
|
+
if(!r.ok){
|
|
412
|
+
return r.json().then(function(j){ throw new Error((j && j.error) || ('HTTP ' + r.status)); }).catch(function(){ throw new Error('HTTP ' + r.status); });
|
|
413
|
+
}
|
|
414
|
+
return r.json();
|
|
415
|
+
})
|
|
416
|
+
.then(function(j){
|
|
417
|
+
if(j && j.redirectTo){ location.assign(j.redirectTo); return; }
|
|
418
|
+
if(!b.hasAttribute('data-refresh')) return;
|
|
419
|
+
var refreshFn = b.getAttribute('data-refresh');
|
|
420
|
+
var refreshArgs = b.getAttribute('data-refresh-args');
|
|
421
|
+
if(refreshArgs !== null){ return refreshByDescriptor(refreshFn, refreshArgs || '[]'); }
|
|
422
|
+
if(refreshFn && refreshFn.trim()){ return refreshTargets(refreshFn); }
|
|
423
|
+
return refreshNearest(b);
|
|
424
|
+
})
|
|
425
|
+
.catch(function(err){ console.error('[kuratchi] client action error:', err); });
|
|
426
|
+
}
|
|
427
|
+
['click','change','input','focus','blur'].forEach(function(ev){ document.addEventListener(ev, act, true); });
|
|
428
|
+
function autoLoadQueries(){
|
|
429
|
+
var seen = Object.create(null);
|
|
430
|
+
by('[data-get][data-as]').forEach(function(el){
|
|
431
|
+
var fn = el.getAttribute('data-get');
|
|
432
|
+
if(!fn) return;
|
|
433
|
+
var args = String(el.getAttribute('data-get-args') || '[]');
|
|
434
|
+
var key = String(fn) + '|' + args;
|
|
435
|
+
if(seen[key]) return;
|
|
436
|
+
seen[key] = true;
|
|
437
|
+
replaceBlocksByDescriptor(fn, args);
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
if(document.readyState === 'loading'){
|
|
441
|
+
document.addEventListener('DOMContentLoaded', autoLoadQueries, { once: true });
|
|
442
|
+
} else {
|
|
443
|
+
autoLoadQueries();
|
|
444
|
+
}
|
|
445
|
+
document.addEventListener('click', function(e){
|
|
446
|
+
var b = e.target && e.target.closest ? e.target.closest('[command="fill-dialog"]') : null;
|
|
447
|
+
if(!b) return;
|
|
448
|
+
var targetId = b.getAttribute('commandfor');
|
|
449
|
+
if(!targetId) return;
|
|
450
|
+
var dialog = document.getElementById(targetId);
|
|
451
|
+
if(!dialog) return;
|
|
452
|
+
var raw = b.getAttribute('data-dialog-data');
|
|
453
|
+
if(!raw) return;
|
|
454
|
+
var data;
|
|
455
|
+
try { data = JSON.parse(raw); } catch(_err) { return; }
|
|
456
|
+
Object.keys(data).forEach(function(k){
|
|
457
|
+
var inp = dialog.querySelector('[name="col_' + k + '"]');
|
|
458
|
+
if(inp){
|
|
459
|
+
inp.value = data[k] === null || data[k] === undefined ? '' : String(data[k]);
|
|
460
|
+
inp.placeholder = data[k] === null || data[k] === undefined ? 'NULL' : '';
|
|
461
|
+
}
|
|
462
|
+
var hidden = dialog.querySelector('#dialog-field-' + k);
|
|
463
|
+
if(hidden){
|
|
464
|
+
hidden.value = data[k] === null || data[k] === undefined ? '' : String(data[k]);
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
var rowidInp = dialog.querySelector('[name="rowid"]');
|
|
468
|
+
if(rowidInp && data.rowid !== undefined) rowidInp.value = String(data.rowid);
|
|
469
|
+
if(typeof dialog.showModal === 'function') dialog.showModal();
|
|
470
|
+
}, true);
|
|
471
|
+
(function initPoll(){
|
|
472
|
+
var prev = {};
|
|
473
|
+
function bindPollEl(el){
|
|
474
|
+
if(!el || !el.getAttribute) return;
|
|
475
|
+
if(el.getAttribute('data-kuratchi-poll-bound') === '1') return;
|
|
476
|
+
var fn = el.getAttribute('data-poll');
|
|
477
|
+
if(!fn) return;
|
|
478
|
+
el.setAttribute('data-kuratchi-poll-bound', '1');
|
|
479
|
+
var args = el.getAttribute('data-poll-args') || '[]';
|
|
480
|
+
var iv = parseInt(el.getAttribute('data-interval') || '', 10) || 3000;
|
|
481
|
+
var key = String(fn) + args;
|
|
482
|
+
if(!(key in prev)) prev[key] = null;
|
|
483
|
+
(function tick(){
|
|
484
|
+
setTimeout(function(){
|
|
485
|
+
fetch(location.pathname + '?_rpc=' + encodeURIComponent(String(fn)) + '&_args=' + encodeURIComponent(args), { headers: { 'x-kuratchi-rpc': '1' } })
|
|
486
|
+
.then(function(r){ return r.json(); })
|
|
487
|
+
.then(function(j){
|
|
488
|
+
if(j && j.ok){
|
|
489
|
+
var s = JSON.stringify(j.data);
|
|
490
|
+
if(prev[key] !== null && prev[key] !== s){ location.reload(); return; }
|
|
491
|
+
prev[key] = s;
|
|
492
|
+
}
|
|
493
|
+
tick();
|
|
494
|
+
})
|
|
495
|
+
.catch(function(){ tick(); });
|
|
496
|
+
}, iv);
|
|
497
|
+
})();
|
|
498
|
+
}
|
|
499
|
+
function scan(){
|
|
500
|
+
by('[data-poll]').forEach(bindPollEl);
|
|
501
|
+
}
|
|
502
|
+
scan();
|
|
503
|
+
setInterval(scan, 500);
|
|
504
|
+
})();
|
|
505
|
+
function confirmClick(e){
|
|
506
|
+
var el = e.target && e.target.closest ? e.target.closest('[confirm]') : null;
|
|
507
|
+
if(!el) return;
|
|
508
|
+
var msg = el.getAttribute('confirm');
|
|
509
|
+
if(!msg) return;
|
|
510
|
+
if(!window.confirm(msg)){ e.preventDefault(); e.stopPropagation(); }
|
|
511
|
+
}
|
|
512
|
+
document.addEventListener('click', confirmClick, true);
|
|
513
|
+
document.addEventListener('submit', function(e){
|
|
514
|
+
var f = e.target && e.target.matches && e.target.matches('form[confirm]') ? e.target : null;
|
|
515
|
+
if(!f) return;
|
|
516
|
+
var msg = f.getAttribute('confirm');
|
|
517
|
+
if(!msg) return;
|
|
518
|
+
if(!window.confirm(msg)){ e.preventDefault(); e.stopPropagation(); }
|
|
519
|
+
}, true);
|
|
520
|
+
document.addEventListener('submit', function(e){
|
|
521
|
+
if(e.defaultPrevented) return;
|
|
522
|
+
var f = e.target;
|
|
523
|
+
if(!f || !f.querySelector) return;
|
|
524
|
+
var aInput = f.querySelector('input[name="_action"]');
|
|
525
|
+
if(!aInput) return;
|
|
526
|
+
var aName = aInput.value;
|
|
527
|
+
if(!aName) return;
|
|
528
|
+
f.setAttribute('data-action-loading', aName);
|
|
529
|
+
Array.prototype.slice.call(f.querySelectorAll('button[type="submit"],button:not([type="button"]):not([type="reset"])')).forEach(function(b){ b.disabled = true; });
|
|
530
|
+
}, true);
|
|
531
|
+
document.addEventListener('change', function(e){
|
|
532
|
+
var t = e.target;
|
|
533
|
+
if(!t || !t.getAttribute) return;
|
|
534
|
+
var gAll = t.getAttribute('data-select-all');
|
|
535
|
+
if(gAll){
|
|
536
|
+
by('[data-select-item]').filter(function(i){ return i.getAttribute('data-select-item') === gAll; }).forEach(function(i){ i.checked = !!t.checked; });
|
|
537
|
+
syncGroup(gAll);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
var gItem = t.getAttribute('data-select-item');
|
|
541
|
+
if(gItem) syncGroup(gItem);
|
|
542
|
+
}, true);
|
|
543
|
+
by('[data-select-all]').forEach(function(m){ var g = m.getAttribute('data-select-all'); if(g) syncGroup(g); });
|
|
544
544
|
})();`;
|
|
545
|
-
const reactiveRuntimeSource = `(function(g){
|
|
546
|
-
if(g.__kuratchiReactive) return;
|
|
547
|
-
const targetMap = new WeakMap();
|
|
548
|
-
const proxyMap = new WeakMap();
|
|
549
|
-
let active = null;
|
|
550
|
-
const queue = new Set();
|
|
551
|
-
let flushing = false;
|
|
552
|
-
function queueRun(fn){
|
|
553
|
-
queue.add(fn);
|
|
554
|
-
if(flushing) return;
|
|
555
|
-
flushing = true;
|
|
556
|
-
Promise.resolve().then(function(){
|
|
557
|
-
try {
|
|
558
|
-
const jobs = Array.from(queue);
|
|
559
|
-
queue.clear();
|
|
560
|
-
for (const job of jobs) job();
|
|
561
|
-
} finally {
|
|
562
|
-
flushing = false;
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
function cleanup(effect){
|
|
567
|
-
const deps = effect.__deps || [];
|
|
568
|
-
for (const dep of deps) dep.delete(effect);
|
|
569
|
-
effect.__deps = [];
|
|
570
|
-
}
|
|
571
|
-
function track(target, key){
|
|
572
|
-
if(!active) return;
|
|
573
|
-
let depsMap = targetMap.get(target);
|
|
574
|
-
if(!depsMap){ depsMap = new Map(); targetMap.set(target, depsMap); }
|
|
575
|
-
let dep = depsMap.get(key);
|
|
576
|
-
if(!dep){ dep = new Set(); depsMap.set(key, dep); }
|
|
577
|
-
if(dep.has(active)) return;
|
|
578
|
-
dep.add(active);
|
|
579
|
-
if(!active.__deps) active.__deps = [];
|
|
580
|
-
active.__deps.push(dep);
|
|
581
|
-
}
|
|
582
|
-
function trigger(target, key){
|
|
583
|
-
const depsMap = targetMap.get(target);
|
|
584
|
-
if(!depsMap) return;
|
|
585
|
-
const effects = new Set();
|
|
586
|
-
const add = function(k){
|
|
587
|
-
const dep = depsMap.get(k);
|
|
588
|
-
if(dep) dep.forEach(function(e){ effects.add(e); });
|
|
589
|
-
};
|
|
590
|
-
add(key);
|
|
591
|
-
add('*');
|
|
592
|
-
effects.forEach(function(e){ queueRun(e); });
|
|
593
|
-
}
|
|
594
|
-
function isObject(value){ return value !== null && typeof value === 'object'; }
|
|
595
|
-
function proxify(value){
|
|
596
|
-
if(!isObject(value)) return value;
|
|
597
|
-
if(proxyMap.has(value)) return proxyMap.get(value);
|
|
598
|
-
const proxy = new Proxy(value, {
|
|
599
|
-
get(target, key, receiver){
|
|
600
|
-
track(target, key);
|
|
601
|
-
const out = Reflect.get(target, key, receiver);
|
|
602
|
-
return isObject(out) ? proxify(out) : out;
|
|
603
|
-
},
|
|
604
|
-
set(target, key, next, receiver){
|
|
605
|
-
const prev = target[key];
|
|
606
|
-
const result = Reflect.set(target, key, next, receiver);
|
|
607
|
-
if(prev !== next) trigger(target, key);
|
|
608
|
-
if(Array.isArray(target) && key !== 'length') trigger(target, 'length');
|
|
609
|
-
return result;
|
|
610
|
-
},
|
|
611
|
-
deleteProperty(target, key){
|
|
612
|
-
const had = Object.prototype.hasOwnProperty.call(target, key);
|
|
613
|
-
const result = Reflect.deleteProperty(target, key);
|
|
614
|
-
if(had) trigger(target, key);
|
|
615
|
-
return result;
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
proxyMap.set(value, proxy);
|
|
619
|
-
return proxy;
|
|
620
|
-
}
|
|
621
|
-
function effect(fn){
|
|
622
|
-
const run = function(){
|
|
623
|
-
cleanup(run);
|
|
624
|
-
active = run;
|
|
625
|
-
try { fn(); } finally { active = null; }
|
|
626
|
-
};
|
|
627
|
-
run.__deps = [];
|
|
628
|
-
run();
|
|
629
|
-
return function(){ cleanup(run); };
|
|
630
|
-
}
|
|
631
|
-
function state(initial){ return proxify(initial); }
|
|
632
|
-
function replace(_prev, next){ return proxify(next); }
|
|
633
|
-
g.__kuratchiReactive = { state, effect, replace };
|
|
545
|
+
const reactiveRuntimeSource = `(function(g){
|
|
546
|
+
if(g.__kuratchiReactive) return;
|
|
547
|
+
const targetMap = new WeakMap();
|
|
548
|
+
const proxyMap = new WeakMap();
|
|
549
|
+
let active = null;
|
|
550
|
+
const queue = new Set();
|
|
551
|
+
let flushing = false;
|
|
552
|
+
function queueRun(fn){
|
|
553
|
+
queue.add(fn);
|
|
554
|
+
if(flushing) return;
|
|
555
|
+
flushing = true;
|
|
556
|
+
Promise.resolve().then(function(){
|
|
557
|
+
try {
|
|
558
|
+
const jobs = Array.from(queue);
|
|
559
|
+
queue.clear();
|
|
560
|
+
for (const job of jobs) job();
|
|
561
|
+
} finally {
|
|
562
|
+
flushing = false;
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
function cleanup(effect){
|
|
567
|
+
const deps = effect.__deps || [];
|
|
568
|
+
for (const dep of deps) dep.delete(effect);
|
|
569
|
+
effect.__deps = [];
|
|
570
|
+
}
|
|
571
|
+
function track(target, key){
|
|
572
|
+
if(!active) return;
|
|
573
|
+
let depsMap = targetMap.get(target);
|
|
574
|
+
if(!depsMap){ depsMap = new Map(); targetMap.set(target, depsMap); }
|
|
575
|
+
let dep = depsMap.get(key);
|
|
576
|
+
if(!dep){ dep = new Set(); depsMap.set(key, dep); }
|
|
577
|
+
if(dep.has(active)) return;
|
|
578
|
+
dep.add(active);
|
|
579
|
+
if(!active.__deps) active.__deps = [];
|
|
580
|
+
active.__deps.push(dep);
|
|
581
|
+
}
|
|
582
|
+
function trigger(target, key){
|
|
583
|
+
const depsMap = targetMap.get(target);
|
|
584
|
+
if(!depsMap) return;
|
|
585
|
+
const effects = new Set();
|
|
586
|
+
const add = function(k){
|
|
587
|
+
const dep = depsMap.get(k);
|
|
588
|
+
if(dep) dep.forEach(function(e){ effects.add(e); });
|
|
589
|
+
};
|
|
590
|
+
add(key);
|
|
591
|
+
add('*');
|
|
592
|
+
effects.forEach(function(e){ queueRun(e); });
|
|
593
|
+
}
|
|
594
|
+
function isObject(value){ return value !== null && typeof value === 'object'; }
|
|
595
|
+
function proxify(value){
|
|
596
|
+
if(!isObject(value)) return value;
|
|
597
|
+
if(proxyMap.has(value)) return proxyMap.get(value);
|
|
598
|
+
const proxy = new Proxy(value, {
|
|
599
|
+
get(target, key, receiver){
|
|
600
|
+
track(target, key);
|
|
601
|
+
const out = Reflect.get(target, key, receiver);
|
|
602
|
+
return isObject(out) ? proxify(out) : out;
|
|
603
|
+
},
|
|
604
|
+
set(target, key, next, receiver){
|
|
605
|
+
const prev = target[key];
|
|
606
|
+
const result = Reflect.set(target, key, next, receiver);
|
|
607
|
+
if(prev !== next) trigger(target, key);
|
|
608
|
+
if(Array.isArray(target) && key !== 'length') trigger(target, 'length');
|
|
609
|
+
return result;
|
|
610
|
+
},
|
|
611
|
+
deleteProperty(target, key){
|
|
612
|
+
const had = Object.prototype.hasOwnProperty.call(target, key);
|
|
613
|
+
const result = Reflect.deleteProperty(target, key);
|
|
614
|
+
if(had) trigger(target, key);
|
|
615
|
+
return result;
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
proxyMap.set(value, proxy);
|
|
619
|
+
return proxy;
|
|
620
|
+
}
|
|
621
|
+
function effect(fn){
|
|
622
|
+
const run = function(){
|
|
623
|
+
cleanup(run);
|
|
624
|
+
active = run;
|
|
625
|
+
try { fn(); } finally { active = null; }
|
|
626
|
+
};
|
|
627
|
+
run.__deps = [];
|
|
628
|
+
run();
|
|
629
|
+
return function(){ cleanup(run); };
|
|
630
|
+
}
|
|
631
|
+
function state(initial){ return proxify(initial); }
|
|
632
|
+
function replace(_prev, next){ return proxify(next); }
|
|
633
|
+
g.__kuratchiReactive = { state, effect, replace };
|
|
634
634
|
})(window);`;
|
|
635
635
|
const actionScript = `<script>${options.isDev ? bridgeSource : compactInlineJs(bridgeSource)}</script>`;
|
|
636
636
|
const reactiveRuntimeScript = `<script>${options.isDev ? reactiveRuntimeSource : compactInlineJs(reactiveRuntimeSource)}</script>`;
|
|
@@ -693,10 +693,10 @@ export function compile(options) {
|
|
|
693
693
|
let layoutScriptBody = stripTopLevelImports(layoutParsed.script);
|
|
694
694
|
const layoutDevDecls = buildDevAliasDeclarations(layoutParsed.devAliases, !!options.isDev);
|
|
695
695
|
layoutScriptBody = [layoutDevDecls, layoutScriptBody].filter(Boolean).join('\n');
|
|
696
|
-
compiledLayout = `function __layout(__content) {
|
|
697
|
-
const __esc = (v) => { if (v == null) return ''; return String(v).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); };
|
|
698
|
-
${layoutScriptBody ? layoutScriptBody + '\n ' : ''}${finalLayoutBody}
|
|
699
|
-
return __html;
|
|
696
|
+
compiledLayout = `function __layout(__content) {
|
|
697
|
+
const __esc = (v) => { if (v == null) return ''; return String(v).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); };
|
|
698
|
+
${layoutScriptBody ? layoutScriptBody + '\n ' : ''}${finalLayoutBody}
|
|
699
|
+
return __html;
|
|
700
700
|
}`;
|
|
701
701
|
}
|
|
702
702
|
else {
|
|
@@ -733,8 +733,9 @@ export function compile(options) {
|
|
|
733
733
|
const authConfig = readAuthConfig(projectDir);
|
|
734
734
|
// Read Durable Object config and discover handler files
|
|
735
735
|
const doConfig = readDoConfig(projectDir);
|
|
736
|
-
|
|
737
|
-
const
|
|
736
|
+
// Auto-discover convention-based worker class files (no config needed)
|
|
737
|
+
const containerConfig = discoverContainerFiles(projectDir);
|
|
738
|
+
const workflowConfig = discoverWorkflowFiles(projectDir);
|
|
738
739
|
const agentConfig = discoverConventionClassFiles(projectDir, path.join('src', 'server'), '.agent.ts', '.agent');
|
|
739
740
|
const doHandlers = doConfig.length > 0
|
|
740
741
|
? discoverDoHandlers(srcDir, doConfig, ormDatabases)
|
|
@@ -1294,6 +1295,12 @@ export function compile(options) {
|
|
|
1294
1295
|
'',
|
|
1295
1296
|
];
|
|
1296
1297
|
writeIfChanged(workerFile, workerLines.join('\n'));
|
|
1298
|
+
// Auto-sync wrangler.jsonc with workflow/container/DO config from kuratchi.config.ts
|
|
1299
|
+
syncWranglerConfig(projectDir, {
|
|
1300
|
+
workflows: workflowConfig,
|
|
1301
|
+
containers: containerConfig,
|
|
1302
|
+
durableObjects: doConfig,
|
|
1303
|
+
});
|
|
1297
1304
|
return workerFile;
|
|
1298
1305
|
}
|
|
1299
1306
|
// �"��"� Helpers �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
@@ -1671,7 +1678,9 @@ function buildRouteObject(opts) {
|
|
|
1671
1678
|
explicitLoadFunction = transpileTypeScript(explicitLoadFunction, `route-load:${pattern}.ts`);
|
|
1672
1679
|
}
|
|
1673
1680
|
const scriptReturnVars = parsed.script
|
|
1674
|
-
? parsed.dataVars.filter((v) => !queryVars.includes(v)
|
|
1681
|
+
? parsed.dataVars.filter((v) => !queryVars.includes(v) &&
|
|
1682
|
+
!parsed.actionFunctions.includes(v) &&
|
|
1683
|
+
!parsed.pollFunctions.includes(v))
|
|
1675
1684
|
: [];
|
|
1676
1685
|
// Load function �" internal server prepass for async route script bodies
|
|
1677
1686
|
// and data-get query state hydration.
|
|
@@ -1723,16 +1732,16 @@ function buildRouteObject(opts) {
|
|
|
1723
1732
|
const queryReturnEntries = queryVars
|
|
1724
1733
|
.filter((name) => !scriptReturnVars.includes(name))
|
|
1725
1734
|
.map((name) => name);
|
|
1726
|
-
returnObj = `
|
|
1735
|
+
returnObj = `
|
|
1727
1736
|
return { ${[...segmentReturnEntries, ...queryReturnEntries].join(', ')} };`;
|
|
1728
1737
|
}
|
|
1729
1738
|
else {
|
|
1730
|
-
returnObj = `
|
|
1739
|
+
returnObj = `
|
|
1731
1740
|
return { ${loadReturnVars.join(', ')} };`;
|
|
1732
1741
|
}
|
|
1733
1742
|
}
|
|
1734
|
-
parts.push(` async load(__routeParams = {}) {
|
|
1735
|
-
${loadBody}${returnObj}
|
|
1743
|
+
parts.push(` async load(__routeParams = {}) {
|
|
1744
|
+
${loadBody}${returnObj}
|
|
1736
1745
|
}`);
|
|
1737
1746
|
}
|
|
1738
1747
|
// Actions �" functions referenced via action={fn} in the template
|
|
@@ -1784,9 +1793,9 @@ function buildRouteObject(opts) {
|
|
|
1784
1793
|
const styleLines = componentStyles.map(css => `__html += \`${css}\\n\`;`);
|
|
1785
1794
|
finalRenderBody = [lines[0], ...styleLines, ...lines.slice(1)].join('\n');
|
|
1786
1795
|
}
|
|
1787
|
-
parts.push(` render(data) {
|
|
1788
|
-
${destructure}${renderPrelude ? renderPrelude + '\n ' : ''}${finalRenderBody}
|
|
1789
|
-
return __html;
|
|
1796
|
+
parts.push(` render(data) {
|
|
1797
|
+
${destructure}${renderPrelude ? renderPrelude + '\n ' : ''}${finalRenderBody}
|
|
1798
|
+
return __html;
|
|
1790
1799
|
}`);
|
|
1791
1800
|
return ` {\n${parts.join(',\n')}\n }`;
|
|
1792
1801
|
}
|
|
@@ -2097,6 +2106,52 @@ function discoverFilesWithSuffix(dir, suffix) {
|
|
|
2097
2106
|
walk(dir);
|
|
2098
2107
|
return out;
|
|
2099
2108
|
}
|
|
2109
|
+
/**
|
|
2110
|
+
* Auto-discover .workflow.ts files in src/server/.
|
|
2111
|
+
* Derives binding name from filename: migration.workflow.ts → MIGRATION_WORKFLOW
|
|
2112
|
+
* Returns entries compatible with WorkerClassConfigEntry for worker.js export generation.
|
|
2113
|
+
*/
|
|
2114
|
+
function discoverWorkflowFiles(projectDir) {
|
|
2115
|
+
const serverDir = path.join(projectDir, 'src', 'server');
|
|
2116
|
+
const files = discoverFilesWithSuffix(serverDir, '.workflow.ts');
|
|
2117
|
+
if (files.length === 0)
|
|
2118
|
+
return [];
|
|
2119
|
+
return files.map((absPath) => {
|
|
2120
|
+
const fileName = path.basename(absPath, '.workflow.ts');
|
|
2121
|
+
// Derive binding: migration.workflow.ts → MIGRATION_WORKFLOW
|
|
2122
|
+
const binding = fileName.toUpperCase().replace(/-/g, '_') + '_WORKFLOW';
|
|
2123
|
+
const resolved = resolveClassExportFromFile(absPath, `.workflow`);
|
|
2124
|
+
return {
|
|
2125
|
+
binding,
|
|
2126
|
+
className: resolved.className,
|
|
2127
|
+
file: path.relative(projectDir, absPath).replace(/\\/g, '/'),
|
|
2128
|
+
exportKind: resolved.exportKind,
|
|
2129
|
+
};
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* Auto-discover .container.ts files in src/server/.
|
|
2134
|
+
* Derives binding name from filename: wordpress.container.ts → WORDPRESS_CONTAINER
|
|
2135
|
+
* Returns entries compatible with WorkerClassConfigEntry for worker.js export generation.
|
|
2136
|
+
*/
|
|
2137
|
+
function discoverContainerFiles(projectDir) {
|
|
2138
|
+
const serverDir = path.join(projectDir, 'src', 'server');
|
|
2139
|
+
const files = discoverFilesWithSuffix(serverDir, '.container.ts');
|
|
2140
|
+
if (files.length === 0)
|
|
2141
|
+
return [];
|
|
2142
|
+
return files.map((absPath) => {
|
|
2143
|
+
const fileName = path.basename(absPath, '.container.ts');
|
|
2144
|
+
// Derive binding: wordpress.container.ts → WORDPRESS_CONTAINER
|
|
2145
|
+
const binding = fileName.toUpperCase().replace(/-/g, '_') + '_CONTAINER';
|
|
2146
|
+
const resolved = resolveClassExportFromFile(absPath, `.container`);
|
|
2147
|
+
return {
|
|
2148
|
+
binding,
|
|
2149
|
+
className: resolved.className,
|
|
2150
|
+
file: path.relative(projectDir, absPath).replace(/\\/g, '/'),
|
|
2151
|
+
exportKind: resolved.exportKind,
|
|
2152
|
+
};
|
|
2153
|
+
});
|
|
2154
|
+
}
|
|
2100
2155
|
/**
|
|
2101
2156
|
* Scan DO handler files.
|
|
2102
2157
|
* - Class mode: default class extends kuratchiDO
|
|
@@ -2414,30 +2469,30 @@ function generateRoutesModule(opts) {
|
|
|
2414
2469
|
let authInit = '';
|
|
2415
2470
|
if (opts.authConfig && opts.authConfig.sessionEnabled) {
|
|
2416
2471
|
const cookieName = opts.authConfig.cookieName;
|
|
2417
|
-
authInit = `
|
|
2418
|
-
// �"��"� Auth Session Init �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2419
|
-
|
|
2420
|
-
function __parseCookies(header) {
|
|
2421
|
-
const map = {};
|
|
2422
|
-
if (!header) return map;
|
|
2423
|
-
for (const pair of header.split(';')) {
|
|
2424
|
-
const eq = pair.indexOf('=');
|
|
2425
|
-
if (eq === -1) continue;
|
|
2426
|
-
map[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
2427
|
-
}
|
|
2428
|
-
return map;
|
|
2429
|
-
}
|
|
2430
|
-
|
|
2431
|
-
function __initAuth(request) {
|
|
2432
|
-
const cookies = __parseCookies(request.headers.get('cookie'));
|
|
2433
|
-
__setLocal('session', null);
|
|
2434
|
-
__setLocal('user', null);
|
|
2435
|
-
__setLocal('auth', {
|
|
2436
|
-
cookies,
|
|
2437
|
-
sessionCookie: cookies['${cookieName}'] || null,
|
|
2438
|
-
cookieName: '${cookieName}',
|
|
2439
|
-
});
|
|
2440
|
-
}
|
|
2472
|
+
authInit = `
|
|
2473
|
+
// �"��"� Auth Session Init �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2474
|
+
|
|
2475
|
+
function __parseCookies(header) {
|
|
2476
|
+
const map = {};
|
|
2477
|
+
if (!header) return map;
|
|
2478
|
+
for (const pair of header.split(';')) {
|
|
2479
|
+
const eq = pair.indexOf('=');
|
|
2480
|
+
if (eq === -1) continue;
|
|
2481
|
+
map[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
2482
|
+
}
|
|
2483
|
+
return map;
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
function __initAuth(request) {
|
|
2487
|
+
const cookies = __parseCookies(request.headers.get('cookie'));
|
|
2488
|
+
__setLocal('session', null);
|
|
2489
|
+
__setLocal('user', null);
|
|
2490
|
+
__setLocal('auth', {
|
|
2491
|
+
cookies,
|
|
2492
|
+
sessionCookie: cookies['${cookieName}'] || null,
|
|
2493
|
+
cookieName: '${cookieName}',
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2441
2496
|
`;
|
|
2442
2497
|
}
|
|
2443
2498
|
const workerImport = `import { WorkerEntrypoint, env as __env } from 'cloudflare:workers';`;
|
|
@@ -2465,38 +2520,38 @@ function __initAuth(request) {
|
|
|
2465
2520
|
`import { kuratchiORM } from '@kuratchi/orm';`,
|
|
2466
2521
|
...schemaImports,
|
|
2467
2522
|
].join('\n');
|
|
2468
|
-
migrationInit = `
|
|
2469
|
-
// �"��"� ORM Auto-Migration �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2470
|
-
|
|
2471
|
-
let __migrated = false;
|
|
2472
|
-
const __ormDatabases = [
|
|
2473
|
-
${migrateEntries.join(',\n')}
|
|
2474
|
-
];
|
|
2475
|
-
|
|
2476
|
-
async function __runMigrations() {
|
|
2477
|
-
if (__migrated) return;
|
|
2478
|
-
__migrated = true;
|
|
2479
|
-
for (const db of __ormDatabases) {
|
|
2480
|
-
const binding = __env[db.binding];
|
|
2481
|
-
if (!binding) continue;
|
|
2482
|
-
try {
|
|
2483
|
-
const executor = (sql, params) => {
|
|
2484
|
-
let stmt = binding.prepare(sql);
|
|
2485
|
-
if (params?.length) stmt = stmt.bind(...params);
|
|
2486
|
-
return stmt.all().then(r => ({ success: r.success ?? true, data: r.results, results: r.results }));
|
|
2487
|
-
};
|
|
2488
|
-
const result = await runMigrations({ execute: executor, schema: db.schema });
|
|
2489
|
-
if (result.applied) {
|
|
2490
|
-
console.log('[kuratchi] ' + db.binding + ': migrated (' + result.statementsRun + ' statements)');
|
|
2491
|
-
}
|
|
2492
|
-
if (result.warnings.length) {
|
|
2493
|
-
result.warnings.forEach(w => console.warn('[kuratchi] ' + db.binding + ': ' + w));
|
|
2494
|
-
}
|
|
2495
|
-
} catch (err) {
|
|
2496
|
-
console.error('[kuratchi] ' + db.binding + ' migration failed:', err.message);
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
}
|
|
2523
|
+
migrationInit = `
|
|
2524
|
+
// �"��"� ORM Auto-Migration �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2525
|
+
|
|
2526
|
+
let __migrated = false;
|
|
2527
|
+
const __ormDatabases = [
|
|
2528
|
+
${migrateEntries.join(',\n')}
|
|
2529
|
+
];
|
|
2530
|
+
|
|
2531
|
+
async function __runMigrations() {
|
|
2532
|
+
if (__migrated) return;
|
|
2533
|
+
__migrated = true;
|
|
2534
|
+
for (const db of __ormDatabases) {
|
|
2535
|
+
const binding = __env[db.binding];
|
|
2536
|
+
if (!binding) continue;
|
|
2537
|
+
try {
|
|
2538
|
+
const executor = (sql, params) => {
|
|
2539
|
+
let stmt = binding.prepare(sql);
|
|
2540
|
+
if (params?.length) stmt = stmt.bind(...params);
|
|
2541
|
+
return stmt.all().then(r => ({ success: r.success ?? true, data: r.results, results: r.results }));
|
|
2542
|
+
};
|
|
2543
|
+
const result = await runMigrations({ execute: executor, schema: db.schema });
|
|
2544
|
+
if (result.applied) {
|
|
2545
|
+
console.log('[kuratchi] ' + db.binding + ': migrated (' + result.statementsRun + ' statements)');
|
|
2546
|
+
}
|
|
2547
|
+
if (result.warnings.length) {
|
|
2548
|
+
result.warnings.forEach(w => console.warn('[kuratchi] ' + db.binding + ': ' + w));
|
|
2549
|
+
}
|
|
2550
|
+
} catch (err) {
|
|
2551
|
+
console.error('[kuratchi] ' + db.binding + ' migration failed:', err.message);
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2500
2555
|
`;
|
|
2501
2556
|
}
|
|
2502
2557
|
}
|
|
@@ -2551,12 +2606,12 @@ async function __runMigrations() {
|
|
|
2551
2606
|
initLines.push(` if (__kuratchiConfig.auth?.organizations) __configOrg(__kuratchiConfig.auth.organizations);`);
|
|
2552
2607
|
}
|
|
2553
2608
|
authPluginImports = imports.join('\n');
|
|
2554
|
-
authPluginInit = `
|
|
2555
|
-
// �"��"� Auth Plugin Init �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2556
|
-
|
|
2557
|
-
function __initAuthPlugins() {
|
|
2558
|
-
${initLines.join('\n')}
|
|
2559
|
-
}
|
|
2609
|
+
authPluginInit = `
|
|
2610
|
+
// �"��"� Auth Plugin Init �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2611
|
+
|
|
2612
|
+
function __initAuthPlugins() {
|
|
2613
|
+
${initLines.join('\n')}
|
|
2614
|
+
}
|
|
2560
2615
|
`;
|
|
2561
2616
|
}
|
|
2562
2617
|
// �"��"� Durable Object class generation �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
@@ -2735,409 +2790,409 @@ ${initLines.join('\n')}
|
|
|
2735
2790
|
doClassCode = `\n// �"��"� Durable Object Classes (generated) �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�\n\n` + doClassLines.join('\n') + '\n';
|
|
2736
2791
|
doResolverInit = `\nfunction __initDoResolvers() {\n${doResolverLines.join('\n')}\n}\n`;
|
|
2737
2792
|
}
|
|
2738
|
-
return `// Generated by KuratchiJS compiler �" do not edit.
|
|
2739
|
-
${opts.isDev ? '\nglobalThis.__kuratchi_DEV__ = true;\n' : ''}
|
|
2740
|
-
${workerImport}
|
|
2741
|
-
${contextImport}
|
|
2742
|
-
${runtimeImport ? runtimeImport + '\n' : ''}${migrationImports ? migrationImports + '\n' : ''}${authPluginImports ? authPluginImports + '\n' : ''}${doImports ? doImports + '\n' : ''}${opts.serverImports.join('\n')}
|
|
2743
|
-
|
|
2744
|
-
// �"��"� Assets �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2745
|
-
|
|
2746
|
-
const __assets = {
|
|
2747
|
-
${opts.compiledAssets.map(a => ` ${JSON.stringify(a.name)}: { content: ${JSON.stringify(a.content)}, mime: ${JSON.stringify(a.mime)}, etag: ${JSON.stringify(a.etag)} }`).join(',\n')}
|
|
2748
|
-
};
|
|
2749
|
-
|
|
2750
|
-
// �"��"� Router �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2751
|
-
|
|
2752
|
-
const __staticRoutes = new Map(); // exact path �' index (O(1) lookup)
|
|
2753
|
-
const __dynamicRoutes = []; // regex-based routes (params/wildcards)
|
|
2754
|
-
|
|
2755
|
-
function __addRoute(pattern, index) {
|
|
2756
|
-
if (!pattern.includes(':') && !pattern.includes('*')) {
|
|
2757
|
-
// Static route �" direct Map lookup, no regex needed
|
|
2758
|
-
__staticRoutes.set(pattern, index);
|
|
2759
|
-
} else {
|
|
2760
|
-
// Dynamic route �" build regex for param extraction
|
|
2761
|
-
const paramNames = [];
|
|
2762
|
-
let regexStr = pattern
|
|
2763
|
-
.replace(/\\*(\\w+)/g, (_, name) => { paramNames.push(name); return '(?<' + name + '>.+)'; })
|
|
2764
|
-
.replace(/:(\\w+)/g, (_, name) => { paramNames.push(name); return '(?<' + name + '>[^/]+)'; });
|
|
2765
|
-
__dynamicRoutes.push({ regex: new RegExp('^' + regexStr + '$'), paramNames, index });
|
|
2766
|
-
}
|
|
2767
|
-
}
|
|
2768
|
-
|
|
2769
|
-
function __match(pathname) {
|
|
2770
|
-
const normalized = pathname === '/' ? '/' : pathname.replace(/\\/$/, '');
|
|
2771
|
-
// Fast path: static routes (most common)
|
|
2772
|
-
const staticIdx = __staticRoutes.get(normalized);
|
|
2773
|
-
if (staticIdx !== undefined) return { params: {}, index: staticIdx };
|
|
2774
|
-
// Slow path: dynamic routes with params
|
|
2775
|
-
for (const route of __dynamicRoutes) {
|
|
2776
|
-
const m = normalized.match(route.regex);
|
|
2777
|
-
if (m) {
|
|
2778
|
-
const params = {};
|
|
2779
|
-
for (const name of route.paramNames) params[name] = m.groups?.[name] ?? '';
|
|
2780
|
-
return { params, index: route.index };
|
|
2781
|
-
}
|
|
2782
|
-
}
|
|
2783
|
-
return null;
|
|
2784
|
-
}
|
|
2785
|
-
|
|
2786
|
-
// �"��"� Layout �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2787
|
-
|
|
2788
|
-
${layoutBlock}
|
|
2789
|
-
|
|
2790
|
-
${layoutActionsBlock}
|
|
2791
|
-
|
|
2792
|
-
// �"��"� Error pages �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2793
|
-
|
|
2794
|
-
const __errorMessages = {
|
|
2795
|
-
400: 'Bad Request',
|
|
2796
|
-
401: 'Unauthorized',
|
|
2797
|
-
403: 'Forbidden',
|
|
2798
|
-
404: 'Not Found',
|
|
2799
|
-
405: 'Method Not Allowed',
|
|
2800
|
-
408: 'Request Timeout',
|
|
2801
|
-
429: 'Too Many Requests',
|
|
2802
|
-
500: 'Internal Server Error',
|
|
2803
|
-
502: 'Bad Gateway',
|
|
2804
|
-
503: 'Service Unavailable',
|
|
2805
|
-
};
|
|
2806
|
-
|
|
2807
|
-
// Built-in default error page �" clean, dark, minimal, centered
|
|
2808
|
-
function __errorPage(status, detail) {
|
|
2809
|
-
const title = __errorMessages[status] || 'Error';
|
|
2810
|
-
const detailHtml = detail ? '<p style="font-family:ui-monospace,monospace;font-size:0.8rem;color:#555;background:#111;padding:0.5rem 1rem;border-radius:6px;max-width:480px;margin:1rem auto 0;word-break:break-word">' + __esc(detail) + '</p>' : '';
|
|
2811
|
-
return '<div style="display:flex;align-items:center;justify-content:center;min-height:60vh;text-align:center;padding:2rem">'
|
|
2812
|
-
+ '<div>'
|
|
2813
|
-
+ '<p style="font-size:5rem;font-weight:700;margin:0;color:#333;line-height:1">' + status + '</p>'
|
|
2814
|
-
+ '<p style="font-size:1rem;color:#555;margin:0.5rem 0 0;letter-spacing:0.05em">' + __esc(title) + '</p>'
|
|
2815
|
-
+ detailHtml
|
|
2816
|
-
+ '</div>'
|
|
2817
|
-
+ '</div>';
|
|
2818
|
-
}
|
|
2819
|
-
|
|
2820
|
-
${customErrorFunctions ? '// Custom error page overrides (user-created NNN.html)\n' + customErrorFunctions + '\n' : ''}
|
|
2821
|
-
// Dispatch: use custom override if it exists, otherwise built-in default
|
|
2822
|
-
const __customErrors = {${Array.from(opts.compiledErrorPages.keys()).map(s => ` ${s}: __error_${s}`).join(',')} };
|
|
2823
|
-
|
|
2824
|
-
function __error(status, detail) {
|
|
2825
|
-
if (__customErrors[status]) return __customErrors[status](detail);
|
|
2826
|
-
return __errorPage(status, detail);
|
|
2827
|
-
}
|
|
2828
|
-
|
|
2829
|
-
${opts.compiledComponents.length > 0 ? '// �"��"� Components �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�\n\n' + opts.compiledComponents.join('\n\n') + '\n' : ''}${migrationInit}${authInit}${authPluginInit}${doResolverInit}${doClassCode}
|
|
2830
|
-
// �"��"� Route definitions �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2831
|
-
|
|
2832
|
-
const routes = [
|
|
2833
|
-
${opts.compiledRoutes.join(',\n')}
|
|
2834
|
-
];
|
|
2835
|
-
|
|
2836
|
-
for (let i = 0; i < routes.length; i++) __addRoute(routes[i].pattern, i);
|
|
2837
|
-
|
|
2838
|
-
// �"��"� Response helpers �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2839
|
-
|
|
2840
|
-
const __defaultSecHeaders = {
|
|
2841
|
-
'X-Content-Type-Options': 'nosniff',
|
|
2842
|
-
'X-Frame-Options': 'DENY',
|
|
2843
|
-
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
2844
|
-
};
|
|
2845
|
-
|
|
2846
|
-
function __secHeaders(response) {
|
|
2847
|
-
for (const [k, v] of Object.entries(__defaultSecHeaders)) {
|
|
2848
|
-
if (!response.headers.has(k)) response.headers.set(k, v);
|
|
2849
|
-
}
|
|
2850
|
-
return response;
|
|
2851
|
-
}
|
|
2852
|
-
|
|
2853
|
-
function __attachCookies(response) {
|
|
2854
|
-
const cookies = __getLocals().__setCookieHeaders;
|
|
2855
|
-
if (cookies && cookies.length > 0) {
|
|
2856
|
-
const newResponse = new Response(response.body, response);
|
|
2857
|
-
for (const h of cookies) newResponse.headers.append('Set-Cookie', h);
|
|
2858
|
-
return __secHeaders(newResponse);
|
|
2859
|
-
}
|
|
2860
|
-
return __secHeaders(response);
|
|
2861
|
-
}
|
|
2862
|
-
|
|
2863
|
-
function __isSameOrigin(request, url) {
|
|
2864
|
-
const fetchSite = request.headers.get('sec-fetch-site');
|
|
2865
|
-
if (fetchSite && fetchSite !== 'same-origin' && fetchSite !== 'same-site' && fetchSite !== 'none') {
|
|
2866
|
-
return false;
|
|
2867
|
-
}
|
|
2868
|
-
const origin = request.headers.get('origin');
|
|
2869
|
-
if (!origin) return true;
|
|
2870
|
-
try { return new URL(origin).origin === url.origin; } catch { return false; }
|
|
2871
|
-
}
|
|
2872
|
-
|
|
2873
|
-
${opts.isLayoutAsync ? 'async ' : ''}function __render(route, data) {
|
|
2874
|
-
let html = route.render(data);
|
|
2875
|
-
const headMatch = html.match(/<head>([\\s\\S]*?)<\\/head>/);
|
|
2876
|
-
if (headMatch) {
|
|
2877
|
-
html = html.replace(headMatch[0], '');
|
|
2878
|
-
const layoutHtml = ${opts.isLayoutAsync ? 'await ' : ''}__layout(html);
|
|
2879
|
-
return __attachCookies(new Response(layoutHtml.replace('</head>', headMatch[1] + '</head>'), {
|
|
2880
|
-
headers: { 'content-type': 'text/html; charset=utf-8' }
|
|
2881
|
-
}));
|
|
2882
|
-
}
|
|
2883
|
-
return __attachCookies(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(html), { headers: { 'content-type': 'text/html; charset=utf-8' } }));
|
|
2884
|
-
}
|
|
2885
|
-
|
|
2886
|
-
const __runtimeDef = (typeof __kuratchiRuntime !== 'undefined' && __kuratchiRuntime && typeof __kuratchiRuntime === 'object') ? __kuratchiRuntime : {};
|
|
2887
|
-
const __runtimeEntries = Object.entries(__runtimeDef).filter(([, step]) => step && typeof step === 'object');
|
|
2888
|
-
|
|
2889
|
-
async function __runRuntimeRequest(ctx, next) {
|
|
2890
|
-
let idx = -1;
|
|
2891
|
-
async function __dispatch(i) {
|
|
2892
|
-
if (i <= idx) throw new Error('[kuratchi runtime] next() called multiple times in request phase');
|
|
2893
|
-
idx = i;
|
|
2894
|
-
const entry = __runtimeEntries[i];
|
|
2895
|
-
if (!entry) return next();
|
|
2896
|
-
const [, step] = entry;
|
|
2897
|
-
if (typeof step.request !== 'function') return __dispatch(i + 1);
|
|
2898
|
-
return await step.request(ctx, () => __dispatch(i + 1));
|
|
2899
|
-
}
|
|
2900
|
-
return __dispatch(0);
|
|
2901
|
-
}
|
|
2902
|
-
|
|
2903
|
-
async function __runRuntimeRoute(ctx, next) {
|
|
2904
|
-
let idx = -1;
|
|
2905
|
-
async function __dispatch(i) {
|
|
2906
|
-
if (i <= idx) throw new Error('[kuratchi runtime] next() called multiple times in route phase');
|
|
2907
|
-
idx = i;
|
|
2908
|
-
const entry = __runtimeEntries[i];
|
|
2909
|
-
if (!entry) return next();
|
|
2910
|
-
const [, step] = entry;
|
|
2911
|
-
if (typeof step.route !== 'function') return __dispatch(i + 1);
|
|
2912
|
-
return await step.route(ctx, () => __dispatch(i + 1));
|
|
2913
|
-
}
|
|
2914
|
-
return __dispatch(0);
|
|
2915
|
-
}
|
|
2916
|
-
|
|
2917
|
-
async function __runRuntimeResponse(ctx, response) {
|
|
2918
|
-
let out = response;
|
|
2919
|
-
for (const [, step] of __runtimeEntries) {
|
|
2920
|
-
if (typeof step.response !== 'function') continue;
|
|
2921
|
-
out = await step.response(ctx, out);
|
|
2922
|
-
if (!(out instanceof Response)) {
|
|
2923
|
-
throw new Error('[kuratchi runtime] response handlers must return a Response');
|
|
2924
|
-
}
|
|
2925
|
-
}
|
|
2926
|
-
return out;
|
|
2927
|
-
}
|
|
2928
|
-
|
|
2929
|
-
async function __runRuntimeError(ctx, error) {
|
|
2930
|
-
for (const [name, step] of __runtimeEntries) {
|
|
2931
|
-
if (typeof step.error !== 'function') continue;
|
|
2932
|
-
try {
|
|
2933
|
-
const handled = await step.error(ctx, error);
|
|
2934
|
-
if (handled instanceof Response) return handled;
|
|
2935
|
-
} catch (hookErr) {
|
|
2936
|
-
console.error('[kuratchi runtime] error handler failed in step', name, hookErr);
|
|
2937
|
-
}
|
|
2938
|
-
}
|
|
2939
|
-
return null;
|
|
2940
|
-
}
|
|
2941
|
-
|
|
2942
|
-
// �"��"� Exported Worker entrypoint �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2943
|
-
|
|
2944
|
-
export default class extends WorkerEntrypoint {
|
|
2945
|
-
async fetch(request) {
|
|
2946
|
-
__setRequestContext(this.ctx, request, __env);
|
|
2947
|
-
${migrationInit ? ' await __runMigrations();\n' : ''}${authInit ? ' __initAuth(request);\n' : ''}${authPluginInit ? ' __initAuthPlugins();\n' : ''}${doResolverInit ? ' __initDoResolvers();\n' : ''}
|
|
2948
|
-
const __runtimeCtx = {
|
|
2949
|
-
request,
|
|
2950
|
-
env: __env,
|
|
2951
|
-
ctx: this.ctx,
|
|
2952
|
-
url: new URL(request.url),
|
|
2953
|
-
params: {},
|
|
2954
|
-
locals: __getLocals(),
|
|
2955
|
-
};
|
|
2956
|
-
|
|
2957
|
-
const __coreFetch = async () => {
|
|
2958
|
-
const request = __runtimeCtx.request;
|
|
2959
|
-
const url = __runtimeCtx.url;
|
|
2960
|
-
${ac?.hasRateLimit ? '\n // Rate limiting - check before route handlers\n { const __rlRes = await __checkRL(); if (__rlRes) return __secHeaders(__rlRes); }\n' : ''}${ac?.hasTurnstile ? ' // Turnstile bot protection\n { const __tsRes = await __checkTS(); if (__tsRes) return __secHeaders(__tsRes); }\n' : ''}${ac?.hasGuards ? ' // Route guards - redirect if not authenticated\n { const __gRes = __checkGuard(); if (__gRes) return __secHeaders(__gRes); }\n' : ''}
|
|
2961
|
-
|
|
2962
|
-
// Serve static assets from src/assets/
|
|
2963
|
-
if (url.pathname.startsWith('${opts.assetsPrefix}')) {
|
|
2964
|
-
const name = url.pathname.slice('${opts.assetsPrefix}'.length);
|
|
2965
|
-
const asset = __assets[name];
|
|
2966
|
-
if (asset) {
|
|
2967
|
-
if (request.headers.get('if-none-match') === asset.etag) {
|
|
2968
|
-
return new Response(null, { status: 304 });
|
|
2969
|
-
}
|
|
2970
|
-
return new Response(asset.content, {
|
|
2971
|
-
headers: { 'content-type': asset.mime, 'cache-control': 'public, max-age=31536000, immutable', 'etag': asset.etag }
|
|
2972
|
-
});
|
|
2973
|
-
}
|
|
2974
|
-
return __secHeaders(new Response('Not Found', { status: 404 }));
|
|
2975
|
-
}
|
|
2976
|
-
|
|
2977
|
-
const match = __match(url.pathname);
|
|
2978
|
-
|
|
2979
|
-
if (!match) {
|
|
2980
|
-
return __secHeaders(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(__error(404)), { status: 404, headers: { 'content-type': 'text/html; charset=utf-8' } }));
|
|
2981
|
-
}
|
|
2982
|
-
|
|
2983
|
-
__runtimeCtx.params = match.params;
|
|
2984
|
-
const route = routes[match.index];
|
|
2985
|
-
__setLocal('params', match.params);
|
|
2986
|
-
|
|
2987
|
-
// API route: dispatch to method handler
|
|
2988
|
-
if (route.__api) {
|
|
2989
|
-
const method = request.method;
|
|
2990
|
-
if (method === 'OPTIONS') {
|
|
2991
|
-
const handler = route['OPTIONS'];
|
|
2992
|
-
if (typeof handler === 'function') return __secHeaders(await handler(__runtimeCtx));
|
|
2993
|
-
const allowed = ['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'].filter(m => typeof route[m] === 'function').join(', ');
|
|
2994
|
-
return __secHeaders(new Response(null, { status: 204, headers: { 'Allow': allowed, 'Access-Control-Allow-Methods': allowed } }));
|
|
2995
|
-
}
|
|
2996
|
-
const handler = route[method];
|
|
2997
|
-
if (typeof handler !== 'function') {
|
|
2998
|
-
const allowed = ['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'].filter(m => typeof route[m] === 'function').join(', ');
|
|
2999
|
-
return __secHeaders(new Response(JSON.stringify({ error: 'Method Not Allowed' }), { status: 405, headers: { 'content-type': 'application/json', 'Allow': allowed } }));
|
|
3000
|
-
}
|
|
3001
|
-
return __secHeaders(await handler(__runtimeCtx));
|
|
3002
|
-
}
|
|
3003
|
-
|
|
3004
|
-
const __qFn = request.headers.get('x-kuratchi-query-fn') || '';
|
|
3005
|
-
const __qArgsRaw = request.headers.get('x-kuratchi-query-args') || '[]';
|
|
3006
|
-
let __qArgs = [];
|
|
3007
|
-
try {
|
|
3008
|
-
const __parsed = JSON.parse(__qArgsRaw);
|
|
3009
|
-
__qArgs = Array.isArray(__parsed) ? __parsed : [];
|
|
3010
|
-
} catch {}
|
|
3011
|
-
__setLocal('__queryOverride', __qFn ? { fn: __qFn, args: __qArgs } : null);
|
|
3012
|
-
if (!__getLocals().__breadcrumbs) {
|
|
3013
|
-
__setLocal('breadcrumbs', __buildDefaultBreadcrumbs(url.pathname, match.params));
|
|
3014
|
-
}
|
|
3015
|
-
|
|
3016
|
-
// RPC call: GET ?_rpc=fnName&_args=[...] -> JSON response
|
|
3017
|
-
const __rpcName = url.searchParams.get('_rpc');
|
|
3018
|
-
if (request.method === 'GET' && __rpcName && route.rpc && Object.hasOwn(route.rpc, __rpcName)) {
|
|
3019
|
-
if (request.headers.get('x-kuratchi-rpc') !== '1') {
|
|
3020
|
-
return __secHeaders(new Response(JSON.stringify({ ok: false, error: 'Forbidden' }), {
|
|
3021
|
-
status: 403, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
3022
|
-
}));
|
|
3023
|
-
}
|
|
3024
|
-
try {
|
|
3025
|
-
const __rpcArgsStr = url.searchParams.get('_args');
|
|
3026
|
-
let __rpcArgs = [];
|
|
3027
|
-
if (__rpcArgsStr) {
|
|
3028
|
-
const __parsed = JSON.parse(__rpcArgsStr);
|
|
3029
|
-
__rpcArgs = Array.isArray(__parsed) ? __parsed : [];
|
|
3030
|
-
}
|
|
3031
|
-
const __rpcResult = await route.rpc[__rpcName](...__rpcArgs);
|
|
3032
|
-
return __secHeaders(new Response(JSON.stringify({ ok: true, data: __rpcResult }), {
|
|
3033
|
-
headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
3034
|
-
}));
|
|
3035
|
-
} catch (err) {
|
|
3036
|
-
console.error('[kuratchi] RPC error:', err);
|
|
3037
|
-
const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' ? err.message : 'Internal Server Error';
|
|
3038
|
-
return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
|
|
3039
|
-
status: 500, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
3040
|
-
}));
|
|
3041
|
-
}
|
|
3042
|
-
}
|
|
3043
|
-
|
|
3044
|
-
// Form action: POST with hidden _action field in form body
|
|
3045
|
-
if (request.method === 'POST') {
|
|
3046
|
-
if (!__isSameOrigin(request, url)) {
|
|
3047
|
-
return __secHeaders(new Response('Forbidden', { status: 403 }));
|
|
3048
|
-
}
|
|
3049
|
-
const formData = await request.formData();
|
|
3050
|
-
const actionName = formData.get('_action');
|
|
3051
|
-
const __actionFn = (actionName && route.actions && Object.hasOwn(route.actions, actionName) ? route.actions[actionName] : null)
|
|
3052
|
-
|| (actionName && __layoutActions && Object.hasOwn(__layoutActions, actionName) ? __layoutActions[actionName] : null);
|
|
3053
|
-
if (actionName && __actionFn) {
|
|
3054
|
-
// Check if this is a fetch-based action call (onclick) with JSON args
|
|
3055
|
-
const argsStr = formData.get('_args');
|
|
3056
|
-
const isFetchAction = argsStr !== null;
|
|
3057
|
-
try {
|
|
3058
|
-
if (isFetchAction) {
|
|
3059
|
-
const __parsed = JSON.parse(argsStr);
|
|
3060
|
-
const args = Array.isArray(__parsed) ? __parsed : [];
|
|
3061
|
-
await __actionFn(...args);
|
|
3062
|
-
} else {
|
|
3063
|
-
await __actionFn(formData);
|
|
3064
|
-
}
|
|
3065
|
-
} catch (err) {
|
|
3066
|
-
if (err && err.isRedirectError) {
|
|
3067
|
-
const __redirectTo = err.location || url.pathname;
|
|
3068
|
-
const __redirectStatus = Number(err.status) || 303;
|
|
3069
|
-
if (isFetchAction) {
|
|
3070
|
-
return __attachCookies(__secHeaders(new Response(JSON.stringify({ ok: true, redirectTo: __redirectTo, redirectStatus: __redirectStatus }), {
|
|
3071
|
-
headers: { 'content-type': 'application/json' }
|
|
3072
|
-
})));
|
|
3073
|
-
}
|
|
3074
|
-
return __attachCookies(new Response(null, { status: __redirectStatus, headers: { 'location': __redirectTo } }));
|
|
3075
|
-
}
|
|
3076
|
-
console.error('[kuratchi] Action error:', err);
|
|
3077
|
-
if (isFetchAction) {
|
|
3078
|
-
const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' && err && err.message ? err.message : 'Internal Server Error';
|
|
3079
|
-
return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
|
|
3080
|
-
status: 500, headers: { 'content-type': 'application/json' }
|
|
3081
|
-
}));
|
|
3082
|
-
}
|
|
3083
|
-
const __loaded = route.load ? await route.load(match.params) : {};
|
|
3084
|
-
const data = (__loaded && typeof __loaded === 'object') ? __loaded : { value: __loaded };
|
|
3085
|
-
data.params = match.params;
|
|
3086
|
-
data.breadcrumbs = __getLocals().__breadcrumbs ?? [];
|
|
3087
|
-
const __allActions = Object.assign({}, route.actions, __layoutActions || {});
|
|
3088
|
-
Object.keys(__allActions).forEach(function(k) { if (!(k in data)) data[k] = { error: undefined, loading: false, success: false }; });
|
|
3089
|
-
const __errMsg = (err && err.isActionError) ? err.message : (typeof __kuratchi_DEV__ !== 'undefined' && err && err.message) ? err.message : 'Action failed';
|
|
3090
|
-
data[actionName] = { error: __errMsg, loading: false, success: false };
|
|
3091
|
-
return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
|
|
3092
|
-
}
|
|
3093
|
-
// Fetch-based actions return lightweight JSON (no page re-render)
|
|
3094
|
-
if (isFetchAction) {
|
|
3095
|
-
return __attachCookies(new Response(JSON.stringify({ ok: true }), {
|
|
3096
|
-
headers: { 'content-type': 'application/json' }
|
|
3097
|
-
}));
|
|
3098
|
-
}
|
|
3099
|
-
// POST-Redirect-GET: redirect to custom target or back to same URL
|
|
3100
|
-
const __locals = __getLocals();
|
|
3101
|
-
const redirectTo = __locals.__redirectTo || url.pathname;
|
|
3102
|
-
const redirectStatus = Number(__locals.__redirectStatus) || 303;
|
|
3103
|
-
return __attachCookies(new Response(null, { status: redirectStatus, headers: { 'location': redirectTo } }));
|
|
3104
|
-
}
|
|
3105
|
-
}
|
|
3106
|
-
|
|
3107
|
-
// GET (or unmatched POST): load + render
|
|
3108
|
-
try {
|
|
3109
|
-
const __loaded = route.load ? await route.load(match.params) : {};
|
|
3110
|
-
const data = (__loaded && typeof __loaded === 'object') ? __loaded : { value: __loaded };
|
|
3111
|
-
data.params = match.params;
|
|
3112
|
-
data.breadcrumbs = __getLocals().__breadcrumbs ?? [];
|
|
3113
|
-
const __allActionsGet = Object.assign({}, route.actions, __layoutActions || {});
|
|
3114
|
-
Object.keys(__allActionsGet).forEach(function(k) { if (!(k in data)) data[k] = { error: undefined, loading: false, success: false }; });
|
|
3115
|
-
return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
|
|
3116
|
-
} catch (err) {
|
|
3117
|
-
if (err && err.isRedirectError) {
|
|
3118
|
-
const __redirectTo = err.location || url.pathname;
|
|
3119
|
-
const __redirectStatus = Number(err.status) || 303;
|
|
3120
|
-
return __attachCookies(new Response(null, { status: __redirectStatus, headers: { 'location': __redirectTo } }));
|
|
3121
|
-
}
|
|
3122
|
-
console.error('[kuratchi] Route load/render error:', err);
|
|
3123
|
-
const __pageErrStatus = (err && err.isPageError && err.status) ? err.status : 500;
|
|
3124
|
-
const __errDetail = (err && err.isPageError) ? err.message : (typeof __kuratchi_DEV__ !== 'undefined' && err && err.message) ? err.message : undefined;
|
|
3125
|
-
return __secHeaders(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(__error(__pageErrStatus, __errDetail)), { status: __pageErrStatus, headers: { 'content-type': 'text/html; charset=utf-8' } }));
|
|
3126
|
-
}
|
|
3127
|
-
};
|
|
3128
|
-
|
|
3129
|
-
try {
|
|
3130
|
-
const __requestResponse = await __runRuntimeRequest(__runtimeCtx, async () => {
|
|
3131
|
-
return __runRuntimeRoute(__runtimeCtx, __coreFetch);
|
|
3132
|
-
});
|
|
3133
|
-
return await __runRuntimeResponse(__runtimeCtx, __requestResponse);
|
|
3134
|
-
} catch (err) {
|
|
3135
|
-
const __handled = await __runRuntimeError(__runtimeCtx, err);
|
|
3136
|
-
if (__handled) return __secHeaders(__handled);
|
|
3137
|
-
throw err;
|
|
3138
|
-
}
|
|
3139
|
-
}
|
|
3140
|
-
}
|
|
2793
|
+
return `// Generated by KuratchiJS compiler �" do not edit.
|
|
2794
|
+
${opts.isDev ? '\nglobalThis.__kuratchi_DEV__ = true;\n' : ''}
|
|
2795
|
+
${workerImport}
|
|
2796
|
+
${contextImport}
|
|
2797
|
+
${runtimeImport ? runtimeImport + '\n' : ''}${migrationImports ? migrationImports + '\n' : ''}${authPluginImports ? authPluginImports + '\n' : ''}${doImports ? doImports + '\n' : ''}${opts.serverImports.join('\n')}
|
|
2798
|
+
|
|
2799
|
+
// �"��"� Assets �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2800
|
+
|
|
2801
|
+
const __assets = {
|
|
2802
|
+
${opts.compiledAssets.map(a => ` ${JSON.stringify(a.name)}: { content: ${JSON.stringify(a.content)}, mime: ${JSON.stringify(a.mime)}, etag: ${JSON.stringify(a.etag)} }`).join(',\n')}
|
|
2803
|
+
};
|
|
2804
|
+
|
|
2805
|
+
// �"��"� Router �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2806
|
+
|
|
2807
|
+
const __staticRoutes = new Map(); // exact path �' index (O(1) lookup)
|
|
2808
|
+
const __dynamicRoutes = []; // regex-based routes (params/wildcards)
|
|
2809
|
+
|
|
2810
|
+
function __addRoute(pattern, index) {
|
|
2811
|
+
if (!pattern.includes(':') && !pattern.includes('*')) {
|
|
2812
|
+
// Static route �" direct Map lookup, no regex needed
|
|
2813
|
+
__staticRoutes.set(pattern, index);
|
|
2814
|
+
} else {
|
|
2815
|
+
// Dynamic route �" build regex for param extraction
|
|
2816
|
+
const paramNames = [];
|
|
2817
|
+
let regexStr = pattern
|
|
2818
|
+
.replace(/\\*(\\w+)/g, (_, name) => { paramNames.push(name); return '(?<' + name + '>.+)'; })
|
|
2819
|
+
.replace(/:(\\w+)/g, (_, name) => { paramNames.push(name); return '(?<' + name + '>[^/]+)'; });
|
|
2820
|
+
__dynamicRoutes.push({ regex: new RegExp('^' + regexStr + '$'), paramNames, index });
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
function __match(pathname) {
|
|
2825
|
+
const normalized = pathname === '/' ? '/' : pathname.replace(/\\/$/, '');
|
|
2826
|
+
// Fast path: static routes (most common)
|
|
2827
|
+
const staticIdx = __staticRoutes.get(normalized);
|
|
2828
|
+
if (staticIdx !== undefined) return { params: {}, index: staticIdx };
|
|
2829
|
+
// Slow path: dynamic routes with params
|
|
2830
|
+
for (const route of __dynamicRoutes) {
|
|
2831
|
+
const m = normalized.match(route.regex);
|
|
2832
|
+
if (m) {
|
|
2833
|
+
const params = {};
|
|
2834
|
+
for (const name of route.paramNames) params[name] = m.groups?.[name] ?? '';
|
|
2835
|
+
return { params, index: route.index };
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
return null;
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// �"��"� Layout �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2842
|
+
|
|
2843
|
+
${layoutBlock}
|
|
2844
|
+
|
|
2845
|
+
${layoutActionsBlock}
|
|
2846
|
+
|
|
2847
|
+
// �"��"� Error pages �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2848
|
+
|
|
2849
|
+
const __errorMessages = {
|
|
2850
|
+
400: 'Bad Request',
|
|
2851
|
+
401: 'Unauthorized',
|
|
2852
|
+
403: 'Forbidden',
|
|
2853
|
+
404: 'Not Found',
|
|
2854
|
+
405: 'Method Not Allowed',
|
|
2855
|
+
408: 'Request Timeout',
|
|
2856
|
+
429: 'Too Many Requests',
|
|
2857
|
+
500: 'Internal Server Error',
|
|
2858
|
+
502: 'Bad Gateway',
|
|
2859
|
+
503: 'Service Unavailable',
|
|
2860
|
+
};
|
|
2861
|
+
|
|
2862
|
+
// Built-in default error page �" clean, dark, minimal, centered
|
|
2863
|
+
function __errorPage(status, detail) {
|
|
2864
|
+
const title = __errorMessages[status] || 'Error';
|
|
2865
|
+
const detailHtml = detail ? '<p style="font-family:ui-monospace,monospace;font-size:0.8rem;color:#555;background:#111;padding:0.5rem 1rem;border-radius:6px;max-width:480px;margin:1rem auto 0;word-break:break-word">' + __esc(detail) + '</p>' : '';
|
|
2866
|
+
return '<div style="display:flex;align-items:center;justify-content:center;min-height:60vh;text-align:center;padding:2rem">'
|
|
2867
|
+
+ '<div>'
|
|
2868
|
+
+ '<p style="font-size:5rem;font-weight:700;margin:0;color:#333;line-height:1">' + status + '</p>'
|
|
2869
|
+
+ '<p style="font-size:1rem;color:#555;margin:0.5rem 0 0;letter-spacing:0.05em">' + __esc(title) + '</p>'
|
|
2870
|
+
+ detailHtml
|
|
2871
|
+
+ '</div>'
|
|
2872
|
+
+ '</div>';
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
${customErrorFunctions ? '// Custom error page overrides (user-created NNN.html)\n' + customErrorFunctions + '\n' : ''}
|
|
2876
|
+
// Dispatch: use custom override if it exists, otherwise built-in default
|
|
2877
|
+
const __customErrors = {${Array.from(opts.compiledErrorPages.keys()).map(s => ` ${s}: __error_${s}`).join(',')} };
|
|
2878
|
+
|
|
2879
|
+
function __error(status, detail) {
|
|
2880
|
+
if (__customErrors[status]) return __customErrors[status](detail);
|
|
2881
|
+
return __errorPage(status, detail);
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
${opts.compiledComponents.length > 0 ? '// �"��"� Components �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�\n\n' + opts.compiledComponents.join('\n\n') + '\n' : ''}${migrationInit}${authInit}${authPluginInit}${doResolverInit}${doClassCode}
|
|
2885
|
+
// �"��"� Route definitions �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2886
|
+
|
|
2887
|
+
const routes = [
|
|
2888
|
+
${opts.compiledRoutes.join(',\n')}
|
|
2889
|
+
];
|
|
2890
|
+
|
|
2891
|
+
for (let i = 0; i < routes.length; i++) __addRoute(routes[i].pattern, i);
|
|
2892
|
+
|
|
2893
|
+
// �"��"� Response helpers �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2894
|
+
|
|
2895
|
+
const __defaultSecHeaders = {
|
|
2896
|
+
'X-Content-Type-Options': 'nosniff',
|
|
2897
|
+
'X-Frame-Options': 'DENY',
|
|
2898
|
+
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
2899
|
+
};
|
|
2900
|
+
|
|
2901
|
+
function __secHeaders(response) {
|
|
2902
|
+
for (const [k, v] of Object.entries(__defaultSecHeaders)) {
|
|
2903
|
+
if (!response.headers.has(k)) response.headers.set(k, v);
|
|
2904
|
+
}
|
|
2905
|
+
return response;
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
function __attachCookies(response) {
|
|
2909
|
+
const cookies = __getLocals().__setCookieHeaders;
|
|
2910
|
+
if (cookies && cookies.length > 0) {
|
|
2911
|
+
const newResponse = new Response(response.body, response);
|
|
2912
|
+
for (const h of cookies) newResponse.headers.append('Set-Cookie', h);
|
|
2913
|
+
return __secHeaders(newResponse);
|
|
2914
|
+
}
|
|
2915
|
+
return __secHeaders(response);
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
function __isSameOrigin(request, url) {
|
|
2919
|
+
const fetchSite = request.headers.get('sec-fetch-site');
|
|
2920
|
+
if (fetchSite && fetchSite !== 'same-origin' && fetchSite !== 'same-site' && fetchSite !== 'none') {
|
|
2921
|
+
return false;
|
|
2922
|
+
}
|
|
2923
|
+
const origin = request.headers.get('origin');
|
|
2924
|
+
if (!origin) return true;
|
|
2925
|
+
try { return new URL(origin).origin === url.origin; } catch { return false; }
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
${opts.isLayoutAsync ? 'async ' : ''}function __render(route, data) {
|
|
2929
|
+
let html = route.render(data);
|
|
2930
|
+
const headMatch = html.match(/<head>([\\s\\S]*?)<\\/head>/);
|
|
2931
|
+
if (headMatch) {
|
|
2932
|
+
html = html.replace(headMatch[0], '');
|
|
2933
|
+
const layoutHtml = ${opts.isLayoutAsync ? 'await ' : ''}__layout(html);
|
|
2934
|
+
return __attachCookies(new Response(layoutHtml.replace('</head>', headMatch[1] + '</head>'), {
|
|
2935
|
+
headers: { 'content-type': 'text/html; charset=utf-8' }
|
|
2936
|
+
}));
|
|
2937
|
+
}
|
|
2938
|
+
return __attachCookies(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(html), { headers: { 'content-type': 'text/html; charset=utf-8' } }));
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
const __runtimeDef = (typeof __kuratchiRuntime !== 'undefined' && __kuratchiRuntime && typeof __kuratchiRuntime === 'object') ? __kuratchiRuntime : {};
|
|
2942
|
+
const __runtimeEntries = Object.entries(__runtimeDef).filter(([, step]) => step && typeof step === 'object');
|
|
2943
|
+
|
|
2944
|
+
async function __runRuntimeRequest(ctx, next) {
|
|
2945
|
+
let idx = -1;
|
|
2946
|
+
async function __dispatch(i) {
|
|
2947
|
+
if (i <= idx) throw new Error('[kuratchi runtime] next() called multiple times in request phase');
|
|
2948
|
+
idx = i;
|
|
2949
|
+
const entry = __runtimeEntries[i];
|
|
2950
|
+
if (!entry) return next();
|
|
2951
|
+
const [, step] = entry;
|
|
2952
|
+
if (typeof step.request !== 'function') return __dispatch(i + 1);
|
|
2953
|
+
return await step.request(ctx, () => __dispatch(i + 1));
|
|
2954
|
+
}
|
|
2955
|
+
return __dispatch(0);
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
async function __runRuntimeRoute(ctx, next) {
|
|
2959
|
+
let idx = -1;
|
|
2960
|
+
async function __dispatch(i) {
|
|
2961
|
+
if (i <= idx) throw new Error('[kuratchi runtime] next() called multiple times in route phase');
|
|
2962
|
+
idx = i;
|
|
2963
|
+
const entry = __runtimeEntries[i];
|
|
2964
|
+
if (!entry) return next();
|
|
2965
|
+
const [, step] = entry;
|
|
2966
|
+
if (typeof step.route !== 'function') return __dispatch(i + 1);
|
|
2967
|
+
return await step.route(ctx, () => __dispatch(i + 1));
|
|
2968
|
+
}
|
|
2969
|
+
return __dispatch(0);
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
async function __runRuntimeResponse(ctx, response) {
|
|
2973
|
+
let out = response;
|
|
2974
|
+
for (const [, step] of __runtimeEntries) {
|
|
2975
|
+
if (typeof step.response !== 'function') continue;
|
|
2976
|
+
out = await step.response(ctx, out);
|
|
2977
|
+
if (!(out instanceof Response)) {
|
|
2978
|
+
throw new Error('[kuratchi runtime] response handlers must return a Response');
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
return out;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
async function __runRuntimeError(ctx, error) {
|
|
2985
|
+
for (const [name, step] of __runtimeEntries) {
|
|
2986
|
+
if (typeof step.error !== 'function') continue;
|
|
2987
|
+
try {
|
|
2988
|
+
const handled = await step.error(ctx, error);
|
|
2989
|
+
if (handled instanceof Response) return handled;
|
|
2990
|
+
} catch (hookErr) {
|
|
2991
|
+
console.error('[kuratchi runtime] error handler failed in step', name, hookErr);
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
return null;
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
// �"��"� Exported Worker entrypoint �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2998
|
+
|
|
2999
|
+
export default class extends WorkerEntrypoint {
|
|
3000
|
+
async fetch(request) {
|
|
3001
|
+
__setRequestContext(this.ctx, request, __env);
|
|
3002
|
+
${migrationInit ? ' await __runMigrations();\n' : ''}${authInit ? ' __initAuth(request);\n' : ''}${authPluginInit ? ' __initAuthPlugins();\n' : ''}${doResolverInit ? ' __initDoResolvers();\n' : ''}
|
|
3003
|
+
const __runtimeCtx = {
|
|
3004
|
+
request,
|
|
3005
|
+
env: __env,
|
|
3006
|
+
ctx: this.ctx,
|
|
3007
|
+
url: new URL(request.url),
|
|
3008
|
+
params: {},
|
|
3009
|
+
locals: __getLocals(),
|
|
3010
|
+
};
|
|
3011
|
+
|
|
3012
|
+
const __coreFetch = async () => {
|
|
3013
|
+
const request = __runtimeCtx.request;
|
|
3014
|
+
const url = __runtimeCtx.url;
|
|
3015
|
+
${ac?.hasRateLimit ? '\n // Rate limiting - check before route handlers\n { const __rlRes = await __checkRL(); if (__rlRes) return __secHeaders(__rlRes); }\n' : ''}${ac?.hasTurnstile ? ' // Turnstile bot protection\n { const __tsRes = await __checkTS(); if (__tsRes) return __secHeaders(__tsRes); }\n' : ''}${ac?.hasGuards ? ' // Route guards - redirect if not authenticated\n { const __gRes = __checkGuard(); if (__gRes) return __secHeaders(__gRes); }\n' : ''}
|
|
3016
|
+
|
|
3017
|
+
// Serve static assets from src/assets/
|
|
3018
|
+
if (url.pathname.startsWith('${opts.assetsPrefix}')) {
|
|
3019
|
+
const name = url.pathname.slice('${opts.assetsPrefix}'.length);
|
|
3020
|
+
const asset = __assets[name];
|
|
3021
|
+
if (asset) {
|
|
3022
|
+
if (request.headers.get('if-none-match') === asset.etag) {
|
|
3023
|
+
return new Response(null, { status: 304 });
|
|
3024
|
+
}
|
|
3025
|
+
return new Response(asset.content, {
|
|
3026
|
+
headers: { 'content-type': asset.mime, 'cache-control': 'public, max-age=31536000, immutable', 'etag': asset.etag }
|
|
3027
|
+
});
|
|
3028
|
+
}
|
|
3029
|
+
return __secHeaders(new Response('Not Found', { status: 404 }));
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
const match = __match(url.pathname);
|
|
3033
|
+
|
|
3034
|
+
if (!match) {
|
|
3035
|
+
return __secHeaders(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(__error(404)), { status: 404, headers: { 'content-type': 'text/html; charset=utf-8' } }));
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
__runtimeCtx.params = match.params;
|
|
3039
|
+
const route = routes[match.index];
|
|
3040
|
+
__setLocal('params', match.params);
|
|
3041
|
+
|
|
3042
|
+
// API route: dispatch to method handler
|
|
3043
|
+
if (route.__api) {
|
|
3044
|
+
const method = request.method;
|
|
3045
|
+
if (method === 'OPTIONS') {
|
|
3046
|
+
const handler = route['OPTIONS'];
|
|
3047
|
+
if (typeof handler === 'function') return __secHeaders(await handler(__runtimeCtx));
|
|
3048
|
+
const allowed = ['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'].filter(m => typeof route[m] === 'function').join(', ');
|
|
3049
|
+
return __secHeaders(new Response(null, { status: 204, headers: { 'Allow': allowed, 'Access-Control-Allow-Methods': allowed } }));
|
|
3050
|
+
}
|
|
3051
|
+
const handler = route[method];
|
|
3052
|
+
if (typeof handler !== 'function') {
|
|
3053
|
+
const allowed = ['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'].filter(m => typeof route[m] === 'function').join(', ');
|
|
3054
|
+
return __secHeaders(new Response(JSON.stringify({ error: 'Method Not Allowed' }), { status: 405, headers: { 'content-type': 'application/json', 'Allow': allowed } }));
|
|
3055
|
+
}
|
|
3056
|
+
return __secHeaders(await handler(__runtimeCtx));
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
const __qFn = request.headers.get('x-kuratchi-query-fn') || '';
|
|
3060
|
+
const __qArgsRaw = request.headers.get('x-kuratchi-query-args') || '[]';
|
|
3061
|
+
let __qArgs = [];
|
|
3062
|
+
try {
|
|
3063
|
+
const __parsed = JSON.parse(__qArgsRaw);
|
|
3064
|
+
__qArgs = Array.isArray(__parsed) ? __parsed : [];
|
|
3065
|
+
} catch {}
|
|
3066
|
+
__setLocal('__queryOverride', __qFn ? { fn: __qFn, args: __qArgs } : null);
|
|
3067
|
+
if (!__getLocals().__breadcrumbs) {
|
|
3068
|
+
__setLocal('breadcrumbs', __buildDefaultBreadcrumbs(url.pathname, match.params));
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
// RPC call: GET ?_rpc=fnName&_args=[...] -> JSON response
|
|
3072
|
+
const __rpcName = url.searchParams.get('_rpc');
|
|
3073
|
+
if (request.method === 'GET' && __rpcName && route.rpc && Object.hasOwn(route.rpc, __rpcName)) {
|
|
3074
|
+
if (request.headers.get('x-kuratchi-rpc') !== '1') {
|
|
3075
|
+
return __secHeaders(new Response(JSON.stringify({ ok: false, error: 'Forbidden' }), {
|
|
3076
|
+
status: 403, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
3077
|
+
}));
|
|
3078
|
+
}
|
|
3079
|
+
try {
|
|
3080
|
+
const __rpcArgsStr = url.searchParams.get('_args');
|
|
3081
|
+
let __rpcArgs = [];
|
|
3082
|
+
if (__rpcArgsStr) {
|
|
3083
|
+
const __parsed = JSON.parse(__rpcArgsStr);
|
|
3084
|
+
__rpcArgs = Array.isArray(__parsed) ? __parsed : [];
|
|
3085
|
+
}
|
|
3086
|
+
const __rpcResult = await route.rpc[__rpcName](...__rpcArgs);
|
|
3087
|
+
return __secHeaders(new Response(JSON.stringify({ ok: true, data: __rpcResult }), {
|
|
3088
|
+
headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
3089
|
+
}));
|
|
3090
|
+
} catch (err) {
|
|
3091
|
+
console.error('[kuratchi] RPC error:', err);
|
|
3092
|
+
const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' ? err.message : 'Internal Server Error';
|
|
3093
|
+
return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
|
|
3094
|
+
status: 500, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
3095
|
+
}));
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3099
|
+
// Form action: POST with hidden _action field in form body
|
|
3100
|
+
if (request.method === 'POST') {
|
|
3101
|
+
if (!__isSameOrigin(request, url)) {
|
|
3102
|
+
return __secHeaders(new Response('Forbidden', { status: 403 }));
|
|
3103
|
+
}
|
|
3104
|
+
const formData = await request.formData();
|
|
3105
|
+
const actionName = formData.get('_action');
|
|
3106
|
+
const __actionFn = (actionName && route.actions && Object.hasOwn(route.actions, actionName) ? route.actions[actionName] : null)
|
|
3107
|
+
|| (actionName && __layoutActions && Object.hasOwn(__layoutActions, actionName) ? __layoutActions[actionName] : null);
|
|
3108
|
+
if (actionName && __actionFn) {
|
|
3109
|
+
// Check if this is a fetch-based action call (onclick) with JSON args
|
|
3110
|
+
const argsStr = formData.get('_args');
|
|
3111
|
+
const isFetchAction = argsStr !== null;
|
|
3112
|
+
try {
|
|
3113
|
+
if (isFetchAction) {
|
|
3114
|
+
const __parsed = JSON.parse(argsStr);
|
|
3115
|
+
const args = Array.isArray(__parsed) ? __parsed : [];
|
|
3116
|
+
await __actionFn(...args);
|
|
3117
|
+
} else {
|
|
3118
|
+
await __actionFn(formData);
|
|
3119
|
+
}
|
|
3120
|
+
} catch (err) {
|
|
3121
|
+
if (err && err.isRedirectError) {
|
|
3122
|
+
const __redirectTo = err.location || url.pathname;
|
|
3123
|
+
const __redirectStatus = Number(err.status) || 303;
|
|
3124
|
+
if (isFetchAction) {
|
|
3125
|
+
return __attachCookies(__secHeaders(new Response(JSON.stringify({ ok: true, redirectTo: __redirectTo, redirectStatus: __redirectStatus }), {
|
|
3126
|
+
headers: { 'content-type': 'application/json' }
|
|
3127
|
+
})));
|
|
3128
|
+
}
|
|
3129
|
+
return __attachCookies(new Response(null, { status: __redirectStatus, headers: { 'location': __redirectTo } }));
|
|
3130
|
+
}
|
|
3131
|
+
console.error('[kuratchi] Action error:', err);
|
|
3132
|
+
if (isFetchAction) {
|
|
3133
|
+
const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' && err && err.message ? err.message : 'Internal Server Error';
|
|
3134
|
+
return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
|
|
3135
|
+
status: 500, headers: { 'content-type': 'application/json' }
|
|
3136
|
+
}));
|
|
3137
|
+
}
|
|
3138
|
+
const __loaded = route.load ? await route.load(match.params) : {};
|
|
3139
|
+
const data = (__loaded && typeof __loaded === 'object') ? __loaded : { value: __loaded };
|
|
3140
|
+
data.params = match.params;
|
|
3141
|
+
data.breadcrumbs = __getLocals().__breadcrumbs ?? [];
|
|
3142
|
+
const __allActions = Object.assign({}, route.actions, __layoutActions || {});
|
|
3143
|
+
Object.keys(__allActions).forEach(function(k) { if (!(k in data)) data[k] = { error: undefined, loading: false, success: false }; });
|
|
3144
|
+
const __errMsg = (err && err.isActionError) ? err.message : (typeof __kuratchi_DEV__ !== 'undefined' && err && err.message) ? err.message : 'Action failed';
|
|
3145
|
+
data[actionName] = { error: __errMsg, loading: false, success: false };
|
|
3146
|
+
return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
|
|
3147
|
+
}
|
|
3148
|
+
// Fetch-based actions return lightweight JSON (no page re-render)
|
|
3149
|
+
if (isFetchAction) {
|
|
3150
|
+
return __attachCookies(new Response(JSON.stringify({ ok: true }), {
|
|
3151
|
+
headers: { 'content-type': 'application/json' }
|
|
3152
|
+
}));
|
|
3153
|
+
}
|
|
3154
|
+
// POST-Redirect-GET: redirect to custom target or back to same URL
|
|
3155
|
+
const __locals = __getLocals();
|
|
3156
|
+
const redirectTo = __locals.__redirectTo || url.pathname;
|
|
3157
|
+
const redirectStatus = Number(__locals.__redirectStatus) || 303;
|
|
3158
|
+
return __attachCookies(new Response(null, { status: redirectStatus, headers: { 'location': redirectTo } }));
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
// GET (or unmatched POST): load + render
|
|
3163
|
+
try {
|
|
3164
|
+
const __loaded = route.load ? await route.load(match.params) : {};
|
|
3165
|
+
const data = (__loaded && typeof __loaded === 'object') ? __loaded : { value: __loaded };
|
|
3166
|
+
data.params = match.params;
|
|
3167
|
+
data.breadcrumbs = __getLocals().__breadcrumbs ?? [];
|
|
3168
|
+
const __allActionsGet = Object.assign({}, route.actions, __layoutActions || {});
|
|
3169
|
+
Object.keys(__allActionsGet).forEach(function(k) { if (!(k in data)) data[k] = { error: undefined, loading: false, success: false }; });
|
|
3170
|
+
return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
|
|
3171
|
+
} catch (err) {
|
|
3172
|
+
if (err && err.isRedirectError) {
|
|
3173
|
+
const __redirectTo = err.location || url.pathname;
|
|
3174
|
+
const __redirectStatus = Number(err.status) || 303;
|
|
3175
|
+
return __attachCookies(new Response(null, { status: __redirectStatus, headers: { 'location': __redirectTo } }));
|
|
3176
|
+
}
|
|
3177
|
+
console.error('[kuratchi] Route load/render error:', err);
|
|
3178
|
+
const __pageErrStatus = (err && err.isPageError && err.status) ? err.status : 500;
|
|
3179
|
+
const __errDetail = (err && err.isPageError) ? err.message : (typeof __kuratchi_DEV__ !== 'undefined' && err && err.message) ? err.message : undefined;
|
|
3180
|
+
return __secHeaders(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(__error(__pageErrStatus, __errDetail)), { status: __pageErrStatus, headers: { 'content-type': 'text/html; charset=utf-8' } }));
|
|
3181
|
+
}
|
|
3182
|
+
};
|
|
3183
|
+
|
|
3184
|
+
try {
|
|
3185
|
+
const __requestResponse = await __runRuntimeRequest(__runtimeCtx, async () => {
|
|
3186
|
+
return __runRuntimeRoute(__runtimeCtx, __coreFetch);
|
|
3187
|
+
});
|
|
3188
|
+
return await __runRuntimeResponse(__runtimeCtx, __requestResponse);
|
|
3189
|
+
} catch (err) {
|
|
3190
|
+
const __handled = await __runRuntimeError(__runtimeCtx, err);
|
|
3191
|
+
if (__handled) return __secHeaders(__handled);
|
|
3192
|
+
throw err;
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3141
3196
|
`;
|
|
3142
3197
|
}
|
|
3143
3198
|
function resolveRuntimeImportPath(projectDir) {
|
|
@@ -3158,3 +3213,216 @@ function toWorkerImportPath(projectDir, outDir, filePath) {
|
|
|
3158
3213
|
rel = `./${rel}`;
|
|
3159
3214
|
return rel.replace(/\.(ts|js|mjs|cjs)$/, '');
|
|
3160
3215
|
}
|
|
3216
|
+
/**
|
|
3217
|
+
* Auto-sync wrangler.jsonc with workflow/container/DO config from kuratchi.config.ts.
|
|
3218
|
+
* This eliminates the need to manually duplicate config between kuratchi.config.ts and wrangler.jsonc.
|
|
3219
|
+
*
|
|
3220
|
+
* The function:
|
|
3221
|
+
* 1. Reads existing wrangler.jsonc (or wrangler.json)
|
|
3222
|
+
* 2. Updates/adds workflow entries based on kuratchi.config.ts
|
|
3223
|
+
* 3. Preserves all other wrangler config (bindings, vars, etc.)
|
|
3224
|
+
* 4. Writes back only if changed
|
|
3225
|
+
*/
|
|
3226
|
+
function syncWranglerConfig(projectDir, config) {
|
|
3227
|
+
// Find wrangler config file (prefer .jsonc, fall back to .json)
|
|
3228
|
+
const jsoncPath = path.join(projectDir, 'wrangler.jsonc');
|
|
3229
|
+
const jsonPath = path.join(projectDir, 'wrangler.json');
|
|
3230
|
+
const tomlPath = path.join(projectDir, 'wrangler.toml');
|
|
3231
|
+
let configPath;
|
|
3232
|
+
let isJsonc = false;
|
|
3233
|
+
if (fs.existsSync(jsoncPath)) {
|
|
3234
|
+
configPath = jsoncPath;
|
|
3235
|
+
isJsonc = true;
|
|
3236
|
+
}
|
|
3237
|
+
else if (fs.existsSync(jsonPath)) {
|
|
3238
|
+
configPath = jsonPath;
|
|
3239
|
+
}
|
|
3240
|
+
else if (fs.existsSync(tomlPath)) {
|
|
3241
|
+
// TOML is not supported for auto-sync — user must migrate to JSON/JSONC
|
|
3242
|
+
console.log('[kuratchi] wrangler.toml detected. Auto-sync requires wrangler.jsonc. Skipping wrangler sync.');
|
|
3243
|
+
return;
|
|
3244
|
+
}
|
|
3245
|
+
else {
|
|
3246
|
+
// No wrangler config exists — create a minimal wrangler.jsonc
|
|
3247
|
+
console.log('[kuratchi] Creating wrangler.jsonc with workflow config...');
|
|
3248
|
+
configPath = jsoncPath;
|
|
3249
|
+
isJsonc = true;
|
|
3250
|
+
}
|
|
3251
|
+
// Read existing config (or start fresh)
|
|
3252
|
+
let rawContent = '';
|
|
3253
|
+
let wranglerConfig = {};
|
|
3254
|
+
if (fs.existsSync(configPath)) {
|
|
3255
|
+
rawContent = fs.readFileSync(configPath, 'utf-8');
|
|
3256
|
+
try {
|
|
3257
|
+
// Strip JSONC comments for parsing
|
|
3258
|
+
const jsonContent = stripJsonComments(rawContent);
|
|
3259
|
+
wranglerConfig = JSON.parse(jsonContent);
|
|
3260
|
+
}
|
|
3261
|
+
catch (err) {
|
|
3262
|
+
console.error(`[kuratchi] Failed to parse ${path.basename(configPath)}: ${err.message}`);
|
|
3263
|
+
console.error('[kuratchi] Skipping wrangler sync. Please fix the JSON syntax.');
|
|
3264
|
+
return;
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
let changed = false;
|
|
3268
|
+
// Sync workflows
|
|
3269
|
+
if (config.workflows.length > 0) {
|
|
3270
|
+
const existingWorkflows = wranglerConfig.workflows || [];
|
|
3271
|
+
const existingByBinding = new Map(existingWorkflows.map(w => [w.binding, w]));
|
|
3272
|
+
for (const wf of config.workflows) {
|
|
3273
|
+
// Convert SCREAMING_SNAKE binding to kebab-case name
|
|
3274
|
+
const name = wf.binding.toLowerCase().replace(/_/g, '-');
|
|
3275
|
+
const entry = {
|
|
3276
|
+
name,
|
|
3277
|
+
binding: wf.binding,
|
|
3278
|
+
class_name: wf.className,
|
|
3279
|
+
};
|
|
3280
|
+
const existing = existingByBinding.get(wf.binding);
|
|
3281
|
+
if (!existing) {
|
|
3282
|
+
existingWorkflows.push(entry);
|
|
3283
|
+
changed = true;
|
|
3284
|
+
console.log(`[kuratchi] Added workflow "${wf.binding}" to wrangler config`);
|
|
3285
|
+
}
|
|
3286
|
+
else if (existing.class_name !== wf.className) {
|
|
3287
|
+
existing.class_name = wf.className;
|
|
3288
|
+
changed = true;
|
|
3289
|
+
console.log(`[kuratchi] Updated workflow "${wf.binding}" class_name to "${wf.className}"`);
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
// Remove workflows that are no longer in kuratchi.config.ts
|
|
3293
|
+
const configBindings = new Set(config.workflows.map(w => w.binding));
|
|
3294
|
+
const filtered = existingWorkflows.filter(w => {
|
|
3295
|
+
if (!configBindings.has(w.binding)) {
|
|
3296
|
+
// Check if this was a kuratchi-managed workflow (has matching naming convention)
|
|
3297
|
+
const expectedName = w.binding.toLowerCase().replace(/_/g, '-');
|
|
3298
|
+
if (w.name === expectedName) {
|
|
3299
|
+
console.log(`[kuratchi] Removed workflow "${w.binding}" from wrangler config`);
|
|
3300
|
+
changed = true;
|
|
3301
|
+
return false;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
return true;
|
|
3305
|
+
});
|
|
3306
|
+
if (filtered.length !== existingWorkflows.length) {
|
|
3307
|
+
wranglerConfig.workflows = filtered;
|
|
3308
|
+
}
|
|
3309
|
+
else {
|
|
3310
|
+
wranglerConfig.workflows = existingWorkflows;
|
|
3311
|
+
}
|
|
3312
|
+
if (wranglerConfig.workflows.length === 0) {
|
|
3313
|
+
delete wranglerConfig.workflows;
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
// Sync containers (similar pattern)
|
|
3317
|
+
if (config.containers.length > 0) {
|
|
3318
|
+
const existingContainers = wranglerConfig.containers || [];
|
|
3319
|
+
const existingByBinding = new Map(existingContainers.map(c => [c.binding, c]));
|
|
3320
|
+
for (const ct of config.containers) {
|
|
3321
|
+
const name = ct.binding.toLowerCase().replace(/_/g, '-');
|
|
3322
|
+
const entry = {
|
|
3323
|
+
name,
|
|
3324
|
+
binding: ct.binding,
|
|
3325
|
+
class_name: ct.className,
|
|
3326
|
+
};
|
|
3327
|
+
const existing = existingByBinding.get(ct.binding);
|
|
3328
|
+
if (!existing) {
|
|
3329
|
+
existingContainers.push(entry);
|
|
3330
|
+
changed = true;
|
|
3331
|
+
console.log(`[kuratchi] Added container "${ct.binding}" to wrangler config`);
|
|
3332
|
+
}
|
|
3333
|
+
else if (existing.class_name !== ct.className) {
|
|
3334
|
+
existing.class_name = ct.className;
|
|
3335
|
+
changed = true;
|
|
3336
|
+
console.log(`[kuratchi] Updated container "${ct.binding}" class_name to "${ct.className}"`);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
wranglerConfig.containers = existingContainers;
|
|
3340
|
+
if (wranglerConfig.containers.length === 0) {
|
|
3341
|
+
delete wranglerConfig.containers;
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
// Sync durable_objects
|
|
3345
|
+
if (config.durableObjects.length > 0) {
|
|
3346
|
+
if (!wranglerConfig.durable_objects) {
|
|
3347
|
+
wranglerConfig.durable_objects = { bindings: [] };
|
|
3348
|
+
}
|
|
3349
|
+
const existingBindings = wranglerConfig.durable_objects.bindings || [];
|
|
3350
|
+
const existingByName = new Map(existingBindings.map(b => [b.name, b]));
|
|
3351
|
+
for (const doEntry of config.durableObjects) {
|
|
3352
|
+
const entry = {
|
|
3353
|
+
name: doEntry.binding,
|
|
3354
|
+
class_name: doEntry.className,
|
|
3355
|
+
};
|
|
3356
|
+
const existing = existingByName.get(doEntry.binding);
|
|
3357
|
+
if (!existing) {
|
|
3358
|
+
existingBindings.push(entry);
|
|
3359
|
+
changed = true;
|
|
3360
|
+
console.log(`[kuratchi] Added durable_object "${doEntry.binding}" to wrangler config`);
|
|
3361
|
+
}
|
|
3362
|
+
else if (existing.class_name !== doEntry.className) {
|
|
3363
|
+
existing.class_name = doEntry.className;
|
|
3364
|
+
changed = true;
|
|
3365
|
+
console.log(`[kuratchi] Updated durable_object "${doEntry.binding}" class_name to "${doEntry.className}"`);
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
wranglerConfig.durable_objects.bindings = existingBindings;
|
|
3369
|
+
}
|
|
3370
|
+
if (!changed)
|
|
3371
|
+
return;
|
|
3372
|
+
// Write back with pretty formatting
|
|
3373
|
+
const newContent = JSON.stringify(wranglerConfig, null, '\t');
|
|
3374
|
+
writeIfChanged(configPath, newContent + '\n');
|
|
3375
|
+
}
|
|
3376
|
+
/**
|
|
3377
|
+
* Strip JSON comments (// and /* *\/) for parsing JSONC files.
|
|
3378
|
+
*/
|
|
3379
|
+
function stripJsonComments(content) {
|
|
3380
|
+
let result = '';
|
|
3381
|
+
let i = 0;
|
|
3382
|
+
let inString = false;
|
|
3383
|
+
let stringChar = '';
|
|
3384
|
+
while (i < content.length) {
|
|
3385
|
+
const ch = content[i];
|
|
3386
|
+
const next = content[i + 1];
|
|
3387
|
+
// Handle string literals
|
|
3388
|
+
if (inString) {
|
|
3389
|
+
result += ch;
|
|
3390
|
+
if (ch === '\\' && i + 1 < content.length) {
|
|
3391
|
+
result += next;
|
|
3392
|
+
i += 2;
|
|
3393
|
+
continue;
|
|
3394
|
+
}
|
|
3395
|
+
if (ch === stringChar) {
|
|
3396
|
+
inString = false;
|
|
3397
|
+
}
|
|
3398
|
+
i++;
|
|
3399
|
+
continue;
|
|
3400
|
+
}
|
|
3401
|
+
// Start of string
|
|
3402
|
+
if (ch === '"' || ch === "'") {
|
|
3403
|
+
inString = true;
|
|
3404
|
+
stringChar = ch;
|
|
3405
|
+
result += ch;
|
|
3406
|
+
i++;
|
|
3407
|
+
continue;
|
|
3408
|
+
}
|
|
3409
|
+
// Line comment
|
|
3410
|
+
if (ch === '/' && next === '/') {
|
|
3411
|
+
// Skip until end of line
|
|
3412
|
+
while (i < content.length && content[i] !== '\n')
|
|
3413
|
+
i++;
|
|
3414
|
+
continue;
|
|
3415
|
+
}
|
|
3416
|
+
// Block comment
|
|
3417
|
+
if (ch === '/' && next === '*') {
|
|
3418
|
+
i += 2;
|
|
3419
|
+
while (i < content.length - 1 && !(content[i] === '*' && content[i + 1] === '/'))
|
|
3420
|
+
i++;
|
|
3421
|
+
i += 2; // Skip */
|
|
3422
|
+
continue;
|
|
3423
|
+
}
|
|
3424
|
+
result += ch;
|
|
3425
|
+
i++;
|
|
3426
|
+
}
|
|
3427
|
+
return result;
|
|
3428
|
+
}
|