@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.
@@ -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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;'); };
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;'); };
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
- const containerConfig = readWorkerClassConfig(projectDir, 'containers');
737
- const workflowConfig = readWorkerClassConfig(projectDir, 'workflows');
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
+ }