@kuratchi/js 0.0.13 → 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 +1122 -856
- 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 �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
@@ -1725,16 +1732,16 @@ function buildRouteObject(opts) {
|
|
|
1725
1732
|
const queryReturnEntries = queryVars
|
|
1726
1733
|
.filter((name) => !scriptReturnVars.includes(name))
|
|
1727
1734
|
.map((name) => name);
|
|
1728
|
-
returnObj = `
|
|
1735
|
+
returnObj = `
|
|
1729
1736
|
return { ${[...segmentReturnEntries, ...queryReturnEntries].join(', ')} };`;
|
|
1730
1737
|
}
|
|
1731
1738
|
else {
|
|
1732
|
-
returnObj = `
|
|
1739
|
+
returnObj = `
|
|
1733
1740
|
return { ${loadReturnVars.join(', ')} };`;
|
|
1734
1741
|
}
|
|
1735
1742
|
}
|
|
1736
|
-
parts.push(` async load(__routeParams = {}) {
|
|
1737
|
-
${loadBody}${returnObj}
|
|
1743
|
+
parts.push(` async load(__routeParams = {}) {
|
|
1744
|
+
${loadBody}${returnObj}
|
|
1738
1745
|
}`);
|
|
1739
1746
|
}
|
|
1740
1747
|
// Actions �" functions referenced via action={fn} in the template
|
|
@@ -1786,9 +1793,9 @@ function buildRouteObject(opts) {
|
|
|
1786
1793
|
const styleLines = componentStyles.map(css => `__html += \`${css}\\n\`;`);
|
|
1787
1794
|
finalRenderBody = [lines[0], ...styleLines, ...lines.slice(1)].join('\n');
|
|
1788
1795
|
}
|
|
1789
|
-
parts.push(` render(data) {
|
|
1790
|
-
${destructure}${renderPrelude ? renderPrelude + '\n ' : ''}${finalRenderBody}
|
|
1791
|
-
return __html;
|
|
1796
|
+
parts.push(` render(data) {
|
|
1797
|
+
${destructure}${renderPrelude ? renderPrelude + '\n ' : ''}${finalRenderBody}
|
|
1798
|
+
return __html;
|
|
1792
1799
|
}`);
|
|
1793
1800
|
return ` {\n${parts.join(',\n')}\n }`;
|
|
1794
1801
|
}
|
|
@@ -2099,6 +2106,52 @@ function discoverFilesWithSuffix(dir, suffix) {
|
|
|
2099
2106
|
walk(dir);
|
|
2100
2107
|
return out;
|
|
2101
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
|
+
}
|
|
2102
2155
|
/**
|
|
2103
2156
|
* Scan DO handler files.
|
|
2104
2157
|
* - Class mode: default class extends kuratchiDO
|
|
@@ -2416,30 +2469,30 @@ function generateRoutesModule(opts) {
|
|
|
2416
2469
|
let authInit = '';
|
|
2417
2470
|
if (opts.authConfig && opts.authConfig.sessionEnabled) {
|
|
2418
2471
|
const cookieName = opts.authConfig.cookieName;
|
|
2419
|
-
authInit = `
|
|
2420
|
-
// �"��"� Auth Session Init �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2421
|
-
|
|
2422
|
-
function __parseCookies(header) {
|
|
2423
|
-
const map = {};
|
|
2424
|
-
if (!header) return map;
|
|
2425
|
-
for (const pair of header.split(';')) {
|
|
2426
|
-
const eq = pair.indexOf('=');
|
|
2427
|
-
if (eq === -1) continue;
|
|
2428
|
-
map[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
2429
|
-
}
|
|
2430
|
-
return map;
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
|
-
function __initAuth(request) {
|
|
2434
|
-
const cookies = __parseCookies(request.headers.get('cookie'));
|
|
2435
|
-
__setLocal('session', null);
|
|
2436
|
-
__setLocal('user', null);
|
|
2437
|
-
__setLocal('auth', {
|
|
2438
|
-
cookies,
|
|
2439
|
-
sessionCookie: cookies['${cookieName}'] || null,
|
|
2440
|
-
cookieName: '${cookieName}',
|
|
2441
|
-
});
|
|
2442
|
-
}
|
|
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
|
+
}
|
|
2443
2496
|
`;
|
|
2444
2497
|
}
|
|
2445
2498
|
const workerImport = `import { WorkerEntrypoint, env as __env } from 'cloudflare:workers';`;
|
|
@@ -2467,38 +2520,38 @@ function __initAuth(request) {
|
|
|
2467
2520
|
`import { kuratchiORM } from '@kuratchi/orm';`,
|
|
2468
2521
|
...schemaImports,
|
|
2469
2522
|
].join('\n');
|
|
2470
|
-
migrationInit = `
|
|
2471
|
-
// �"��"� ORM Auto-Migration �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2472
|
-
|
|
2473
|
-
let __migrated = false;
|
|
2474
|
-
const __ormDatabases = [
|
|
2475
|
-
${migrateEntries.join(',\n')}
|
|
2476
|
-
];
|
|
2477
|
-
|
|
2478
|
-
async function __runMigrations() {
|
|
2479
|
-
if (__migrated) return;
|
|
2480
|
-
__migrated = true;
|
|
2481
|
-
for (const db of __ormDatabases) {
|
|
2482
|
-
const binding = __env[db.binding];
|
|
2483
|
-
if (!binding) continue;
|
|
2484
|
-
try {
|
|
2485
|
-
const executor = (sql, params) => {
|
|
2486
|
-
let stmt = binding.prepare(sql);
|
|
2487
|
-
if (params?.length) stmt = stmt.bind(...params);
|
|
2488
|
-
return stmt.all().then(r => ({ success: r.success ?? true, data: r.results, results: r.results }));
|
|
2489
|
-
};
|
|
2490
|
-
const result = await runMigrations({ execute: executor, schema: db.schema });
|
|
2491
|
-
if (result.applied) {
|
|
2492
|
-
console.log('[kuratchi] ' + db.binding + ': migrated (' + result.statementsRun + ' statements)');
|
|
2493
|
-
}
|
|
2494
|
-
if (result.warnings.length) {
|
|
2495
|
-
result.warnings.forEach(w => console.warn('[kuratchi] ' + db.binding + ': ' + w));
|
|
2496
|
-
}
|
|
2497
|
-
} catch (err) {
|
|
2498
|
-
console.error('[kuratchi] ' + db.binding + ' migration failed:', err.message);
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2501
|
-
}
|
|
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
|
+
}
|
|
2502
2555
|
`;
|
|
2503
2556
|
}
|
|
2504
2557
|
}
|
|
@@ -2553,12 +2606,12 @@ async function __runMigrations() {
|
|
|
2553
2606
|
initLines.push(` if (__kuratchiConfig.auth?.organizations) __configOrg(__kuratchiConfig.auth.organizations);`);
|
|
2554
2607
|
}
|
|
2555
2608
|
authPluginImports = imports.join('\n');
|
|
2556
|
-
authPluginInit = `
|
|
2557
|
-
// �"��"� Auth Plugin Init �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2558
|
-
|
|
2559
|
-
function __initAuthPlugins() {
|
|
2560
|
-
${initLines.join('\n')}
|
|
2561
|
-
}
|
|
2609
|
+
authPluginInit = `
|
|
2610
|
+
// �"��"� Auth Plugin Init �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2611
|
+
|
|
2612
|
+
function __initAuthPlugins() {
|
|
2613
|
+
${initLines.join('\n')}
|
|
2614
|
+
}
|
|
2562
2615
|
`;
|
|
2563
2616
|
}
|
|
2564
2617
|
// �"��"� Durable Object class generation �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
@@ -2737,409 +2790,409 @@ ${initLines.join('\n')}
|
|
|
2737
2790
|
doClassCode = `\n// �"��"� Durable Object Classes (generated) �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�\n\n` + doClassLines.join('\n') + '\n';
|
|
2738
2791
|
doResolverInit = `\nfunction __initDoResolvers() {\n${doResolverLines.join('\n')}\n}\n`;
|
|
2739
2792
|
}
|
|
2740
|
-
return `// Generated by KuratchiJS compiler �" do not edit.
|
|
2741
|
-
${opts.isDev ? '\nglobalThis.__kuratchi_DEV__ = true;\n' : ''}
|
|
2742
|
-
${workerImport}
|
|
2743
|
-
${contextImport}
|
|
2744
|
-
${runtimeImport ? runtimeImport + '\n' : ''}${migrationImports ? migrationImports + '\n' : ''}${authPluginImports ? authPluginImports + '\n' : ''}${doImports ? doImports + '\n' : ''}${opts.serverImports.join('\n')}
|
|
2745
|
-
|
|
2746
|
-
// �"��"� Assets �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2747
|
-
|
|
2748
|
-
const __assets = {
|
|
2749
|
-
${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')}
|
|
2750
|
-
};
|
|
2751
|
-
|
|
2752
|
-
// �"��"� Router �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2753
|
-
|
|
2754
|
-
const __staticRoutes = new Map(); // exact path �' index (O(1) lookup)
|
|
2755
|
-
const __dynamicRoutes = []; // regex-based routes (params/wildcards)
|
|
2756
|
-
|
|
2757
|
-
function __addRoute(pattern, index) {
|
|
2758
|
-
if (!pattern.includes(':') && !pattern.includes('*')) {
|
|
2759
|
-
// Static route �" direct Map lookup, no regex needed
|
|
2760
|
-
__staticRoutes.set(pattern, index);
|
|
2761
|
-
} else {
|
|
2762
|
-
// Dynamic route �" build regex for param extraction
|
|
2763
|
-
const paramNames = [];
|
|
2764
|
-
let regexStr = pattern
|
|
2765
|
-
.replace(/\\*(\\w+)/g, (_, name) => { paramNames.push(name); return '(?<' + name + '>.+)'; })
|
|
2766
|
-
.replace(/:(\\w+)/g, (_, name) => { paramNames.push(name); return '(?<' + name + '>[^/]+)'; });
|
|
2767
|
-
__dynamicRoutes.push({ regex: new RegExp('^' + regexStr + '$'), paramNames, index });
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
|
|
2771
|
-
function __match(pathname) {
|
|
2772
|
-
const normalized = pathname === '/' ? '/' : pathname.replace(/\\/$/, '');
|
|
2773
|
-
// Fast path: static routes (most common)
|
|
2774
|
-
const staticIdx = __staticRoutes.get(normalized);
|
|
2775
|
-
if (staticIdx !== undefined) return { params: {}, index: staticIdx };
|
|
2776
|
-
// Slow path: dynamic routes with params
|
|
2777
|
-
for (const route of __dynamicRoutes) {
|
|
2778
|
-
const m = normalized.match(route.regex);
|
|
2779
|
-
if (m) {
|
|
2780
|
-
const params = {};
|
|
2781
|
-
for (const name of route.paramNames) params[name] = m.groups?.[name] ?? '';
|
|
2782
|
-
return { params, index: route.index };
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
return null;
|
|
2786
|
-
}
|
|
2787
|
-
|
|
2788
|
-
// �"��"� Layout �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2789
|
-
|
|
2790
|
-
${layoutBlock}
|
|
2791
|
-
|
|
2792
|
-
${layoutActionsBlock}
|
|
2793
|
-
|
|
2794
|
-
// �"��"� Error pages �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2795
|
-
|
|
2796
|
-
const __errorMessages = {
|
|
2797
|
-
400: 'Bad Request',
|
|
2798
|
-
401: 'Unauthorized',
|
|
2799
|
-
403: 'Forbidden',
|
|
2800
|
-
404: 'Not Found',
|
|
2801
|
-
405: 'Method Not Allowed',
|
|
2802
|
-
408: 'Request Timeout',
|
|
2803
|
-
429: 'Too Many Requests',
|
|
2804
|
-
500: 'Internal Server Error',
|
|
2805
|
-
502: 'Bad Gateway',
|
|
2806
|
-
503: 'Service Unavailable',
|
|
2807
|
-
};
|
|
2808
|
-
|
|
2809
|
-
// Built-in default error page �" clean, dark, minimal, centered
|
|
2810
|
-
function __errorPage(status, detail) {
|
|
2811
|
-
const title = __errorMessages[status] || 'Error';
|
|
2812
|
-
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>' : '';
|
|
2813
|
-
return '<div style="display:flex;align-items:center;justify-content:center;min-height:60vh;text-align:center;padding:2rem">'
|
|
2814
|
-
+ '<div>'
|
|
2815
|
-
+ '<p style="font-size:5rem;font-weight:700;margin:0;color:#333;line-height:1">' + status + '</p>'
|
|
2816
|
-
+ '<p style="font-size:1rem;color:#555;margin:0.5rem 0 0;letter-spacing:0.05em">' + __esc(title) + '</p>'
|
|
2817
|
-
+ detailHtml
|
|
2818
|
-
+ '</div>'
|
|
2819
|
-
+ '</div>';
|
|
2820
|
-
}
|
|
2821
|
-
|
|
2822
|
-
${customErrorFunctions ? '// Custom error page overrides (user-created NNN.html)\n' + customErrorFunctions + '\n' : ''}
|
|
2823
|
-
// Dispatch: use custom override if it exists, otherwise built-in default
|
|
2824
|
-
const __customErrors = {${Array.from(opts.compiledErrorPages.keys()).map(s => ` ${s}: __error_${s}`).join(',')} };
|
|
2825
|
-
|
|
2826
|
-
function __error(status, detail) {
|
|
2827
|
-
if (__customErrors[status]) return __customErrors[status](detail);
|
|
2828
|
-
return __errorPage(status, detail);
|
|
2829
|
-
}
|
|
2830
|
-
|
|
2831
|
-
${opts.compiledComponents.length > 0 ? '// �"��"� Components �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�\n\n' + opts.compiledComponents.join('\n\n') + '\n' : ''}${migrationInit}${authInit}${authPluginInit}${doResolverInit}${doClassCode}
|
|
2832
|
-
// �"��"� Route definitions �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2833
|
-
|
|
2834
|
-
const routes = [
|
|
2835
|
-
${opts.compiledRoutes.join(',\n')}
|
|
2836
|
-
];
|
|
2837
|
-
|
|
2838
|
-
for (let i = 0; i < routes.length; i++) __addRoute(routes[i].pattern, i);
|
|
2839
|
-
|
|
2840
|
-
// �"��"� Response helpers �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2841
|
-
|
|
2842
|
-
const __defaultSecHeaders = {
|
|
2843
|
-
'X-Content-Type-Options': 'nosniff',
|
|
2844
|
-
'X-Frame-Options': 'DENY',
|
|
2845
|
-
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
2846
|
-
};
|
|
2847
|
-
|
|
2848
|
-
function __secHeaders(response) {
|
|
2849
|
-
for (const [k, v] of Object.entries(__defaultSecHeaders)) {
|
|
2850
|
-
if (!response.headers.has(k)) response.headers.set(k, v);
|
|
2851
|
-
}
|
|
2852
|
-
return response;
|
|
2853
|
-
}
|
|
2854
|
-
|
|
2855
|
-
function __attachCookies(response) {
|
|
2856
|
-
const cookies = __getLocals().__setCookieHeaders;
|
|
2857
|
-
if (cookies && cookies.length > 0) {
|
|
2858
|
-
const newResponse = new Response(response.body, response);
|
|
2859
|
-
for (const h of cookies) newResponse.headers.append('Set-Cookie', h);
|
|
2860
|
-
return __secHeaders(newResponse);
|
|
2861
|
-
}
|
|
2862
|
-
return __secHeaders(response);
|
|
2863
|
-
}
|
|
2864
|
-
|
|
2865
|
-
function __isSameOrigin(request, url) {
|
|
2866
|
-
const fetchSite = request.headers.get('sec-fetch-site');
|
|
2867
|
-
if (fetchSite && fetchSite !== 'same-origin' && fetchSite !== 'same-site' && fetchSite !== 'none') {
|
|
2868
|
-
return false;
|
|
2869
|
-
}
|
|
2870
|
-
const origin = request.headers.get('origin');
|
|
2871
|
-
if (!origin) return true;
|
|
2872
|
-
try { return new URL(origin).origin === url.origin; } catch { return false; }
|
|
2873
|
-
}
|
|
2874
|
-
|
|
2875
|
-
${opts.isLayoutAsync ? 'async ' : ''}function __render(route, data) {
|
|
2876
|
-
let html = route.render(data);
|
|
2877
|
-
const headMatch = html.match(/<head>([\\s\\S]*?)<\\/head>/);
|
|
2878
|
-
if (headMatch) {
|
|
2879
|
-
html = html.replace(headMatch[0], '');
|
|
2880
|
-
const layoutHtml = ${opts.isLayoutAsync ? 'await ' : ''}__layout(html);
|
|
2881
|
-
return __attachCookies(new Response(layoutHtml.replace('</head>', headMatch[1] + '</head>'), {
|
|
2882
|
-
headers: { 'content-type': 'text/html; charset=utf-8' }
|
|
2883
|
-
}));
|
|
2884
|
-
}
|
|
2885
|
-
return __attachCookies(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(html), { headers: { 'content-type': 'text/html; charset=utf-8' } }));
|
|
2886
|
-
}
|
|
2887
|
-
|
|
2888
|
-
const __runtimeDef = (typeof __kuratchiRuntime !== 'undefined' && __kuratchiRuntime && typeof __kuratchiRuntime === 'object') ? __kuratchiRuntime : {};
|
|
2889
|
-
const __runtimeEntries = Object.entries(__runtimeDef).filter(([, step]) => step && typeof step === 'object');
|
|
2890
|
-
|
|
2891
|
-
async function __runRuntimeRequest(ctx, next) {
|
|
2892
|
-
let idx = -1;
|
|
2893
|
-
async function __dispatch(i) {
|
|
2894
|
-
if (i <= idx) throw new Error('[kuratchi runtime] next() called multiple times in request phase');
|
|
2895
|
-
idx = i;
|
|
2896
|
-
const entry = __runtimeEntries[i];
|
|
2897
|
-
if (!entry) return next();
|
|
2898
|
-
const [, step] = entry;
|
|
2899
|
-
if (typeof step.request !== 'function') return __dispatch(i + 1);
|
|
2900
|
-
return await step.request(ctx, () => __dispatch(i + 1));
|
|
2901
|
-
}
|
|
2902
|
-
return __dispatch(0);
|
|
2903
|
-
}
|
|
2904
|
-
|
|
2905
|
-
async function __runRuntimeRoute(ctx, next) {
|
|
2906
|
-
let idx = -1;
|
|
2907
|
-
async function __dispatch(i) {
|
|
2908
|
-
if (i <= idx) throw new Error('[kuratchi runtime] next() called multiple times in route phase');
|
|
2909
|
-
idx = i;
|
|
2910
|
-
const entry = __runtimeEntries[i];
|
|
2911
|
-
if (!entry) return next();
|
|
2912
|
-
const [, step] = entry;
|
|
2913
|
-
if (typeof step.route !== 'function') return __dispatch(i + 1);
|
|
2914
|
-
return await step.route(ctx, () => __dispatch(i + 1));
|
|
2915
|
-
}
|
|
2916
|
-
return __dispatch(0);
|
|
2917
|
-
}
|
|
2918
|
-
|
|
2919
|
-
async function __runRuntimeResponse(ctx, response) {
|
|
2920
|
-
let out = response;
|
|
2921
|
-
for (const [, step] of __runtimeEntries) {
|
|
2922
|
-
if (typeof step.response !== 'function') continue;
|
|
2923
|
-
out = await step.response(ctx, out);
|
|
2924
|
-
if (!(out instanceof Response)) {
|
|
2925
|
-
throw new Error('[kuratchi runtime] response handlers must return a Response');
|
|
2926
|
-
}
|
|
2927
|
-
}
|
|
2928
|
-
return out;
|
|
2929
|
-
}
|
|
2930
|
-
|
|
2931
|
-
async function __runRuntimeError(ctx, error) {
|
|
2932
|
-
for (const [name, step] of __runtimeEntries) {
|
|
2933
|
-
if (typeof step.error !== 'function') continue;
|
|
2934
|
-
try {
|
|
2935
|
-
const handled = await step.error(ctx, error);
|
|
2936
|
-
if (handled instanceof Response) return handled;
|
|
2937
|
-
} catch (hookErr) {
|
|
2938
|
-
console.error('[kuratchi runtime] error handler failed in step', name, hookErr);
|
|
2939
|
-
}
|
|
2940
|
-
}
|
|
2941
|
-
return null;
|
|
2942
|
-
}
|
|
2943
|
-
|
|
2944
|
-
// �"��"� Exported Worker entrypoint �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
|
|
2945
|
-
|
|
2946
|
-
export default class extends WorkerEntrypoint {
|
|
2947
|
-
async fetch(request) {
|
|
2948
|
-
__setRequestContext(this.ctx, request, __env);
|
|
2949
|
-
${migrationInit ? ' await __runMigrations();\n' : ''}${authInit ? ' __initAuth(request);\n' : ''}${authPluginInit ? ' __initAuthPlugins();\n' : ''}${doResolverInit ? ' __initDoResolvers();\n' : ''}
|
|
2950
|
-
const __runtimeCtx = {
|
|
2951
|
-
request,
|
|
2952
|
-
env: __env,
|
|
2953
|
-
ctx: this.ctx,
|
|
2954
|
-
url: new URL(request.url),
|
|
2955
|
-
params: {},
|
|
2956
|
-
locals: __getLocals(),
|
|
2957
|
-
};
|
|
2958
|
-
|
|
2959
|
-
const __coreFetch = async () => {
|
|
2960
|
-
const request = __runtimeCtx.request;
|
|
2961
|
-
const url = __runtimeCtx.url;
|
|
2962
|
-
${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' : ''}
|
|
2963
|
-
|
|
2964
|
-
// Serve static assets from src/assets/
|
|
2965
|
-
if (url.pathname.startsWith('${opts.assetsPrefix}')) {
|
|
2966
|
-
const name = url.pathname.slice('${opts.assetsPrefix}'.length);
|
|
2967
|
-
const asset = __assets[name];
|
|
2968
|
-
if (asset) {
|
|
2969
|
-
if (request.headers.get('if-none-match') === asset.etag) {
|
|
2970
|
-
return new Response(null, { status: 304 });
|
|
2971
|
-
}
|
|
2972
|
-
return new Response(asset.content, {
|
|
2973
|
-
headers: { 'content-type': asset.mime, 'cache-control': 'public, max-age=31536000, immutable', 'etag': asset.etag }
|
|
2974
|
-
});
|
|
2975
|
-
}
|
|
2976
|
-
return __secHeaders(new Response('Not Found', { status: 404 }));
|
|
2977
|
-
}
|
|
2978
|
-
|
|
2979
|
-
const match = __match(url.pathname);
|
|
2980
|
-
|
|
2981
|
-
if (!match) {
|
|
2982
|
-
return __secHeaders(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(__error(404)), { status: 404, headers: { 'content-type': 'text/html; charset=utf-8' } }));
|
|
2983
|
-
}
|
|
2984
|
-
|
|
2985
|
-
__runtimeCtx.params = match.params;
|
|
2986
|
-
const route = routes[match.index];
|
|
2987
|
-
__setLocal('params', match.params);
|
|
2988
|
-
|
|
2989
|
-
// API route: dispatch to method handler
|
|
2990
|
-
if (route.__api) {
|
|
2991
|
-
const method = request.method;
|
|
2992
|
-
if (method === 'OPTIONS') {
|
|
2993
|
-
const handler = route['OPTIONS'];
|
|
2994
|
-
if (typeof handler === 'function') return __secHeaders(await handler(__runtimeCtx));
|
|
2995
|
-
const allowed = ['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'].filter(m => typeof route[m] === 'function').join(', ');
|
|
2996
|
-
return __secHeaders(new Response(null, { status: 204, headers: { 'Allow': allowed, 'Access-Control-Allow-Methods': allowed } }));
|
|
2997
|
-
}
|
|
2998
|
-
const handler = route[method];
|
|
2999
|
-
if (typeof handler !== 'function') {
|
|
3000
|
-
const allowed = ['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'].filter(m => typeof route[m] === 'function').join(', ');
|
|
3001
|
-
return __secHeaders(new Response(JSON.stringify({ error: 'Method Not Allowed' }), { status: 405, headers: { 'content-type': 'application/json', 'Allow': allowed } }));
|
|
3002
|
-
}
|
|
3003
|
-
return __secHeaders(await handler(__runtimeCtx));
|
|
3004
|
-
}
|
|
3005
|
-
|
|
3006
|
-
const __qFn = request.headers.get('x-kuratchi-query-fn') || '';
|
|
3007
|
-
const __qArgsRaw = request.headers.get('x-kuratchi-query-args') || '[]';
|
|
3008
|
-
let __qArgs = [];
|
|
3009
|
-
try {
|
|
3010
|
-
const __parsed = JSON.parse(__qArgsRaw);
|
|
3011
|
-
__qArgs = Array.isArray(__parsed) ? __parsed : [];
|
|
3012
|
-
} catch {}
|
|
3013
|
-
__setLocal('__queryOverride', __qFn ? { fn: __qFn, args: __qArgs } : null);
|
|
3014
|
-
if (!__getLocals().__breadcrumbs) {
|
|
3015
|
-
__setLocal('breadcrumbs', __buildDefaultBreadcrumbs(url.pathname, match.params));
|
|
3016
|
-
}
|
|
3017
|
-
|
|
3018
|
-
// RPC call: GET ?_rpc=fnName&_args=[...] -> JSON response
|
|
3019
|
-
const __rpcName = url.searchParams.get('_rpc');
|
|
3020
|
-
if (request.method === 'GET' && __rpcName && route.rpc && Object.hasOwn(route.rpc, __rpcName)) {
|
|
3021
|
-
if (request.headers.get('x-kuratchi-rpc') !== '1') {
|
|
3022
|
-
return __secHeaders(new Response(JSON.stringify({ ok: false, error: 'Forbidden' }), {
|
|
3023
|
-
status: 403, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
3024
|
-
}));
|
|
3025
|
-
}
|
|
3026
|
-
try {
|
|
3027
|
-
const __rpcArgsStr = url.searchParams.get('_args');
|
|
3028
|
-
let __rpcArgs = [];
|
|
3029
|
-
if (__rpcArgsStr) {
|
|
3030
|
-
const __parsed = JSON.parse(__rpcArgsStr);
|
|
3031
|
-
__rpcArgs = Array.isArray(__parsed) ? __parsed : [];
|
|
3032
|
-
}
|
|
3033
|
-
const __rpcResult = await route.rpc[__rpcName](...__rpcArgs);
|
|
3034
|
-
return __secHeaders(new Response(JSON.stringify({ ok: true, data: __rpcResult }), {
|
|
3035
|
-
headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
3036
|
-
}));
|
|
3037
|
-
} catch (err) {
|
|
3038
|
-
console.error('[kuratchi] RPC error:', err);
|
|
3039
|
-
const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' ? err.message : 'Internal Server Error';
|
|
3040
|
-
return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
|
|
3041
|
-
status: 500, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
3042
|
-
}));
|
|
3043
|
-
}
|
|
3044
|
-
}
|
|
3045
|
-
|
|
3046
|
-
// Form action: POST with hidden _action field in form body
|
|
3047
|
-
if (request.method === 'POST') {
|
|
3048
|
-
if (!__isSameOrigin(request, url)) {
|
|
3049
|
-
return __secHeaders(new Response('Forbidden', { status: 403 }));
|
|
3050
|
-
}
|
|
3051
|
-
const formData = await request.formData();
|
|
3052
|
-
const actionName = formData.get('_action');
|
|
3053
|
-
const __actionFn = (actionName && route.actions && Object.hasOwn(route.actions, actionName) ? route.actions[actionName] : null)
|
|
3054
|
-
|| (actionName && __layoutActions && Object.hasOwn(__layoutActions, actionName) ? __layoutActions[actionName] : null);
|
|
3055
|
-
if (actionName && __actionFn) {
|
|
3056
|
-
// Check if this is a fetch-based action call (onclick) with JSON args
|
|
3057
|
-
const argsStr = formData.get('_args');
|
|
3058
|
-
const isFetchAction = argsStr !== null;
|
|
3059
|
-
try {
|
|
3060
|
-
if (isFetchAction) {
|
|
3061
|
-
const __parsed = JSON.parse(argsStr);
|
|
3062
|
-
const args = Array.isArray(__parsed) ? __parsed : [];
|
|
3063
|
-
await __actionFn(...args);
|
|
3064
|
-
} else {
|
|
3065
|
-
await __actionFn(formData);
|
|
3066
|
-
}
|
|
3067
|
-
} catch (err) {
|
|
3068
|
-
if (err && err.isRedirectError) {
|
|
3069
|
-
const __redirectTo = err.location || url.pathname;
|
|
3070
|
-
const __redirectStatus = Number(err.status) || 303;
|
|
3071
|
-
if (isFetchAction) {
|
|
3072
|
-
return __attachCookies(__secHeaders(new Response(JSON.stringify({ ok: true, redirectTo: __redirectTo, redirectStatus: __redirectStatus }), {
|
|
3073
|
-
headers: { 'content-type': 'application/json' }
|
|
3074
|
-
})));
|
|
3075
|
-
}
|
|
3076
|
-
return __attachCookies(new Response(null, { status: __redirectStatus, headers: { 'location': __redirectTo } }));
|
|
3077
|
-
}
|
|
3078
|
-
console.error('[kuratchi] Action error:', err);
|
|
3079
|
-
if (isFetchAction) {
|
|
3080
|
-
const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' && err && err.message ? err.message : 'Internal Server Error';
|
|
3081
|
-
return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
|
|
3082
|
-
status: 500, headers: { 'content-type': 'application/json' }
|
|
3083
|
-
}));
|
|
3084
|
-
}
|
|
3085
|
-
const __loaded = route.load ? await route.load(match.params) : {};
|
|
3086
|
-
const data = (__loaded && typeof __loaded === 'object') ? __loaded : { value: __loaded };
|
|
3087
|
-
data.params = match.params;
|
|
3088
|
-
data.breadcrumbs = __getLocals().__breadcrumbs ?? [];
|
|
3089
|
-
const __allActions = Object.assign({}, route.actions, __layoutActions || {});
|
|
3090
|
-
Object.keys(__allActions).forEach(function(k) { if (!(k in data)) data[k] = { error: undefined, loading: false, success: false }; });
|
|
3091
|
-
const __errMsg = (err && err.isActionError) ? err.message : (typeof __kuratchi_DEV__ !== 'undefined' && err && err.message) ? err.message : 'Action failed';
|
|
3092
|
-
data[actionName] = { error: __errMsg, loading: false, success: false };
|
|
3093
|
-
return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
|
|
3094
|
-
}
|
|
3095
|
-
// Fetch-based actions return lightweight JSON (no page re-render)
|
|
3096
|
-
if (isFetchAction) {
|
|
3097
|
-
return __attachCookies(new Response(JSON.stringify({ ok: true }), {
|
|
3098
|
-
headers: { 'content-type': 'application/json' }
|
|
3099
|
-
}));
|
|
3100
|
-
}
|
|
3101
|
-
// POST-Redirect-GET: redirect to custom target or back to same URL
|
|
3102
|
-
const __locals = __getLocals();
|
|
3103
|
-
const redirectTo = __locals.__redirectTo || url.pathname;
|
|
3104
|
-
const redirectStatus = Number(__locals.__redirectStatus) || 303;
|
|
3105
|
-
return __attachCookies(new Response(null, { status: redirectStatus, headers: { 'location': redirectTo } }));
|
|
3106
|
-
}
|
|
3107
|
-
}
|
|
3108
|
-
|
|
3109
|
-
// GET (or unmatched POST): load + render
|
|
3110
|
-
try {
|
|
3111
|
-
const __loaded = route.load ? await route.load(match.params) : {};
|
|
3112
|
-
const data = (__loaded && typeof __loaded === 'object') ? __loaded : { value: __loaded };
|
|
3113
|
-
data.params = match.params;
|
|
3114
|
-
data.breadcrumbs = __getLocals().__breadcrumbs ?? [];
|
|
3115
|
-
const __allActionsGet = Object.assign({}, route.actions, __layoutActions || {});
|
|
3116
|
-
Object.keys(__allActionsGet).forEach(function(k) { if (!(k in data)) data[k] = { error: undefined, loading: false, success: false }; });
|
|
3117
|
-
return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
|
|
3118
|
-
} catch (err) {
|
|
3119
|
-
if (err && err.isRedirectError) {
|
|
3120
|
-
const __redirectTo = err.location || url.pathname;
|
|
3121
|
-
const __redirectStatus = Number(err.status) || 303;
|
|
3122
|
-
return __attachCookies(new Response(null, { status: __redirectStatus, headers: { 'location': __redirectTo } }));
|
|
3123
|
-
}
|
|
3124
|
-
console.error('[kuratchi] Route load/render error:', err);
|
|
3125
|
-
const __pageErrStatus = (err && err.isPageError && err.status) ? err.status : 500;
|
|
3126
|
-
const __errDetail = (err && err.isPageError) ? err.message : (typeof __kuratchi_DEV__ !== 'undefined' && err && err.message) ? err.message : undefined;
|
|
3127
|
-
return __secHeaders(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(__error(__pageErrStatus, __errDetail)), { status: __pageErrStatus, headers: { 'content-type': 'text/html; charset=utf-8' } }));
|
|
3128
|
-
}
|
|
3129
|
-
};
|
|
3130
|
-
|
|
3131
|
-
try {
|
|
3132
|
-
const __requestResponse = await __runRuntimeRequest(__runtimeCtx, async () => {
|
|
3133
|
-
return __runRuntimeRoute(__runtimeCtx, __coreFetch);
|
|
3134
|
-
});
|
|
3135
|
-
return await __runRuntimeResponse(__runtimeCtx, __requestResponse);
|
|
3136
|
-
} catch (err) {
|
|
3137
|
-
const __handled = await __runRuntimeError(__runtimeCtx, err);
|
|
3138
|
-
if (__handled) return __secHeaders(__handled);
|
|
3139
|
-
throw err;
|
|
3140
|
-
}
|
|
3141
|
-
}
|
|
3142
|
-
}
|
|
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
|
+
}
|
|
3143
3196
|
`;
|
|
3144
3197
|
}
|
|
3145
3198
|
function resolveRuntimeImportPath(projectDir) {
|
|
@@ -3160,3 +3213,216 @@ function toWorkerImportPath(projectDir, outDir, filePath) {
|
|
|
3160
3213
|
rel = `./${rel}`;
|
|
3161
3214
|
return rel.replace(/\.(ts|js|mjs|cjs)$/, '');
|
|
3162
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
|
+
}
|