@kuratchi/js 0.0.12 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
@@ -1671,7 +1678,9 @@ function buildRouteObject(opts) {
1671
1678
  explicitLoadFunction = transpileTypeScript(explicitLoadFunction, `route-load:${pattern}.ts`);
1672
1679
  }
1673
1680
  const scriptReturnVars = parsed.script
1674
- ? parsed.dataVars.filter((v) => !queryVars.includes(v))
1681
+ ? parsed.dataVars.filter((v) => !queryVars.includes(v) &&
1682
+ !parsed.actionFunctions.includes(v) &&
1683
+ !parsed.pollFunctions.includes(v))
1675
1684
  : [];
1676
1685
  // Load function �" internal server prepass for async route script bodies
1677
1686
  // and data-get query state hydration.
@@ -1723,16 +1732,16 @@ function buildRouteObject(opts) {
1723
1732
  const queryReturnEntries = queryVars
1724
1733
  .filter((name) => !scriptReturnVars.includes(name))
1725
1734
  .map((name) => name);
1726
- returnObj = `
1735
+ returnObj = `
1727
1736
  return { ${[...segmentReturnEntries, ...queryReturnEntries].join(', ')} };`;
1728
1737
  }
1729
1738
  else {
1730
- returnObj = `
1739
+ returnObj = `
1731
1740
  return { ${loadReturnVars.join(', ')} };`;
1732
1741
  }
1733
1742
  }
1734
- parts.push(` async load(__routeParams = {}) {
1735
- ${loadBody}${returnObj}
1743
+ parts.push(` async load(__routeParams = {}) {
1744
+ ${loadBody}${returnObj}
1736
1745
  }`);
1737
1746
  }
1738
1747
  // Actions �" functions referenced via action={fn} in the template
@@ -1784,9 +1793,9 @@ function buildRouteObject(opts) {
1784
1793
  const styleLines = componentStyles.map(css => `__html += \`${css}\\n\`;`);
1785
1794
  finalRenderBody = [lines[0], ...styleLines, ...lines.slice(1)].join('\n');
1786
1795
  }
1787
- parts.push(` render(data) {
1788
- ${destructure}${renderPrelude ? renderPrelude + '\n ' : ''}${finalRenderBody}
1789
- return __html;
1796
+ parts.push(` render(data) {
1797
+ ${destructure}${renderPrelude ? renderPrelude + '\n ' : ''}${finalRenderBody}
1798
+ return __html;
1790
1799
  }`);
1791
1800
  return ` {\n${parts.join(',\n')}\n }`;
1792
1801
  }
@@ -2097,6 +2106,52 @@ function discoverFilesWithSuffix(dir, suffix) {
2097
2106
  walk(dir);
2098
2107
  return out;
2099
2108
  }
2109
+ /**
2110
+ * Auto-discover .workflow.ts files in src/server/.
2111
+ * Derives binding name from filename: migration.workflow.ts → MIGRATION_WORKFLOW
2112
+ * Returns entries compatible with WorkerClassConfigEntry for worker.js export generation.
2113
+ */
2114
+ function discoverWorkflowFiles(projectDir) {
2115
+ const serverDir = path.join(projectDir, 'src', 'server');
2116
+ const files = discoverFilesWithSuffix(serverDir, '.workflow.ts');
2117
+ if (files.length === 0)
2118
+ return [];
2119
+ return files.map((absPath) => {
2120
+ const fileName = path.basename(absPath, '.workflow.ts');
2121
+ // Derive binding: migration.workflow.ts → MIGRATION_WORKFLOW
2122
+ const binding = fileName.toUpperCase().replace(/-/g, '_') + '_WORKFLOW';
2123
+ const resolved = resolveClassExportFromFile(absPath, `.workflow`);
2124
+ return {
2125
+ binding,
2126
+ className: resolved.className,
2127
+ file: path.relative(projectDir, absPath).replace(/\\/g, '/'),
2128
+ exportKind: resolved.exportKind,
2129
+ };
2130
+ });
2131
+ }
2132
+ /**
2133
+ * Auto-discover .container.ts files in src/server/.
2134
+ * Derives binding name from filename: wordpress.container.ts → WORDPRESS_CONTAINER
2135
+ * Returns entries compatible with WorkerClassConfigEntry for worker.js export generation.
2136
+ */
2137
+ function discoverContainerFiles(projectDir) {
2138
+ const serverDir = path.join(projectDir, 'src', 'server');
2139
+ const files = discoverFilesWithSuffix(serverDir, '.container.ts');
2140
+ if (files.length === 0)
2141
+ return [];
2142
+ return files.map((absPath) => {
2143
+ const fileName = path.basename(absPath, '.container.ts');
2144
+ // Derive binding: wordpress.container.ts → WORDPRESS_CONTAINER
2145
+ const binding = fileName.toUpperCase().replace(/-/g, '_') + '_CONTAINER';
2146
+ const resolved = resolveClassExportFromFile(absPath, `.container`);
2147
+ return {
2148
+ binding,
2149
+ className: resolved.className,
2150
+ file: path.relative(projectDir, absPath).replace(/\\/g, '/'),
2151
+ exportKind: resolved.exportKind,
2152
+ };
2153
+ });
2154
+ }
2100
2155
  /**
2101
2156
  * Scan DO handler files.
2102
2157
  * - Class mode: default class extends kuratchiDO
@@ -2414,30 +2469,30 @@ function generateRoutesModule(opts) {
2414
2469
  let authInit = '';
2415
2470
  if (opts.authConfig && opts.authConfig.sessionEnabled) {
2416
2471
  const cookieName = opts.authConfig.cookieName;
2417
- authInit = `
2418
- // �"��"� Auth Session Init �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2419
-
2420
- function __parseCookies(header) {
2421
- const map = {};
2422
- if (!header) return map;
2423
- for (const pair of header.split(';')) {
2424
- const eq = pair.indexOf('=');
2425
- if (eq === -1) continue;
2426
- map[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
2427
- }
2428
- return map;
2429
- }
2430
-
2431
- function __initAuth(request) {
2432
- const cookies = __parseCookies(request.headers.get('cookie'));
2433
- __setLocal('session', null);
2434
- __setLocal('user', null);
2435
- __setLocal('auth', {
2436
- cookies,
2437
- sessionCookie: cookies['${cookieName}'] || null,
2438
- cookieName: '${cookieName}',
2439
- });
2440
- }
2472
+ authInit = `
2473
+ // �"��"� Auth Session Init �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2474
+
2475
+ function __parseCookies(header) {
2476
+ const map = {};
2477
+ if (!header) return map;
2478
+ for (const pair of header.split(';')) {
2479
+ const eq = pair.indexOf('=');
2480
+ if (eq === -1) continue;
2481
+ map[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
2482
+ }
2483
+ return map;
2484
+ }
2485
+
2486
+ function __initAuth(request) {
2487
+ const cookies = __parseCookies(request.headers.get('cookie'));
2488
+ __setLocal('session', null);
2489
+ __setLocal('user', null);
2490
+ __setLocal('auth', {
2491
+ cookies,
2492
+ sessionCookie: cookies['${cookieName}'] || null,
2493
+ cookieName: '${cookieName}',
2494
+ });
2495
+ }
2441
2496
  `;
2442
2497
  }
2443
2498
  const workerImport = `import { WorkerEntrypoint, env as __env } from 'cloudflare:workers';`;
@@ -2465,38 +2520,38 @@ function __initAuth(request) {
2465
2520
  `import { kuratchiORM } from '@kuratchi/orm';`,
2466
2521
  ...schemaImports,
2467
2522
  ].join('\n');
2468
- migrationInit = `
2469
- // �"��"� ORM Auto-Migration �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2470
-
2471
- let __migrated = false;
2472
- const __ormDatabases = [
2473
- ${migrateEntries.join(',\n')}
2474
- ];
2475
-
2476
- async function __runMigrations() {
2477
- if (__migrated) return;
2478
- __migrated = true;
2479
- for (const db of __ormDatabases) {
2480
- const binding = __env[db.binding];
2481
- if (!binding) continue;
2482
- try {
2483
- const executor = (sql, params) => {
2484
- let stmt = binding.prepare(sql);
2485
- if (params?.length) stmt = stmt.bind(...params);
2486
- return stmt.all().then(r => ({ success: r.success ?? true, data: r.results, results: r.results }));
2487
- };
2488
- const result = await runMigrations({ execute: executor, schema: db.schema });
2489
- if (result.applied) {
2490
- console.log('[kuratchi] ' + db.binding + ': migrated (' + result.statementsRun + ' statements)');
2491
- }
2492
- if (result.warnings.length) {
2493
- result.warnings.forEach(w => console.warn('[kuratchi] ' + db.binding + ': ' + w));
2494
- }
2495
- } catch (err) {
2496
- console.error('[kuratchi] ' + db.binding + ' migration failed:', err.message);
2497
- }
2498
- }
2499
- }
2523
+ migrationInit = `
2524
+ // �"��"� ORM Auto-Migration �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2525
+
2526
+ let __migrated = false;
2527
+ const __ormDatabases = [
2528
+ ${migrateEntries.join(',\n')}
2529
+ ];
2530
+
2531
+ async function __runMigrations() {
2532
+ if (__migrated) return;
2533
+ __migrated = true;
2534
+ for (const db of __ormDatabases) {
2535
+ const binding = __env[db.binding];
2536
+ if (!binding) continue;
2537
+ try {
2538
+ const executor = (sql, params) => {
2539
+ let stmt = binding.prepare(sql);
2540
+ if (params?.length) stmt = stmt.bind(...params);
2541
+ return stmt.all().then(r => ({ success: r.success ?? true, data: r.results, results: r.results }));
2542
+ };
2543
+ const result = await runMigrations({ execute: executor, schema: db.schema });
2544
+ if (result.applied) {
2545
+ console.log('[kuratchi] ' + db.binding + ': migrated (' + result.statementsRun + ' statements)');
2546
+ }
2547
+ if (result.warnings.length) {
2548
+ result.warnings.forEach(w => console.warn('[kuratchi] ' + db.binding + ': ' + w));
2549
+ }
2550
+ } catch (err) {
2551
+ console.error('[kuratchi] ' + db.binding + ' migration failed:', err.message);
2552
+ }
2553
+ }
2554
+ }
2500
2555
  `;
2501
2556
  }
2502
2557
  }
@@ -2551,12 +2606,12 @@ async function __runMigrations() {
2551
2606
  initLines.push(` if (__kuratchiConfig.auth?.organizations) __configOrg(__kuratchiConfig.auth.organizations);`);
2552
2607
  }
2553
2608
  authPluginImports = imports.join('\n');
2554
- authPluginInit = `
2555
- // �"��"� Auth Plugin Init �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2556
-
2557
- function __initAuthPlugins() {
2558
- ${initLines.join('\n')}
2559
- }
2609
+ authPluginInit = `
2610
+ // �"��"� Auth Plugin Init �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2611
+
2612
+ function __initAuthPlugins() {
2613
+ ${initLines.join('\n')}
2614
+ }
2560
2615
  `;
2561
2616
  }
2562
2617
  // �"��"� Durable Object class generation �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
@@ -2735,409 +2790,409 @@ ${initLines.join('\n')}
2735
2790
  doClassCode = `\n// �"��"� Durable Object Classes (generated) �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�\n\n` + doClassLines.join('\n') + '\n';
2736
2791
  doResolverInit = `\nfunction __initDoResolvers() {\n${doResolverLines.join('\n')}\n}\n`;
2737
2792
  }
2738
- return `// Generated by KuratchiJS compiler �" do not edit.
2739
- ${opts.isDev ? '\nglobalThis.__kuratchi_DEV__ = true;\n' : ''}
2740
- ${workerImport}
2741
- ${contextImport}
2742
- ${runtimeImport ? runtimeImport + '\n' : ''}${migrationImports ? migrationImports + '\n' : ''}${authPluginImports ? authPluginImports + '\n' : ''}${doImports ? doImports + '\n' : ''}${opts.serverImports.join('\n')}
2743
-
2744
- // �"��"� Assets �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2745
-
2746
- const __assets = {
2747
- ${opts.compiledAssets.map(a => ` ${JSON.stringify(a.name)}: { content: ${JSON.stringify(a.content)}, mime: ${JSON.stringify(a.mime)}, etag: ${JSON.stringify(a.etag)} }`).join(',\n')}
2748
- };
2749
-
2750
- // �"��"� Router �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2751
-
2752
- const __staticRoutes = new Map(); // exact path �' index (O(1) lookup)
2753
- const __dynamicRoutes = []; // regex-based routes (params/wildcards)
2754
-
2755
- function __addRoute(pattern, index) {
2756
- if (!pattern.includes(':') && !pattern.includes('*')) {
2757
- // Static route �" direct Map lookup, no regex needed
2758
- __staticRoutes.set(pattern, index);
2759
- } else {
2760
- // Dynamic route �" build regex for param extraction
2761
- const paramNames = [];
2762
- let regexStr = pattern
2763
- .replace(/\\*(\\w+)/g, (_, name) => { paramNames.push(name); return '(?<' + name + '>.+)'; })
2764
- .replace(/:(\\w+)/g, (_, name) => { paramNames.push(name); return '(?<' + name + '>[^/]+)'; });
2765
- __dynamicRoutes.push({ regex: new RegExp('^' + regexStr + '$'), paramNames, index });
2766
- }
2767
- }
2768
-
2769
- function __match(pathname) {
2770
- const normalized = pathname === '/' ? '/' : pathname.replace(/\\/$/, '');
2771
- // Fast path: static routes (most common)
2772
- const staticIdx = __staticRoutes.get(normalized);
2773
- if (staticIdx !== undefined) return { params: {}, index: staticIdx };
2774
- // Slow path: dynamic routes with params
2775
- for (const route of __dynamicRoutes) {
2776
- const m = normalized.match(route.regex);
2777
- if (m) {
2778
- const params = {};
2779
- for (const name of route.paramNames) params[name] = m.groups?.[name] ?? '';
2780
- return { params, index: route.index };
2781
- }
2782
- }
2783
- return null;
2784
- }
2785
-
2786
- // �"��"� Layout �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2787
-
2788
- ${layoutBlock}
2789
-
2790
- ${layoutActionsBlock}
2791
-
2792
- // �"��"� Error pages �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2793
-
2794
- const __errorMessages = {
2795
- 400: 'Bad Request',
2796
- 401: 'Unauthorized',
2797
- 403: 'Forbidden',
2798
- 404: 'Not Found',
2799
- 405: 'Method Not Allowed',
2800
- 408: 'Request Timeout',
2801
- 429: 'Too Many Requests',
2802
- 500: 'Internal Server Error',
2803
- 502: 'Bad Gateway',
2804
- 503: 'Service Unavailable',
2805
- };
2806
-
2807
- // Built-in default error page �" clean, dark, minimal, centered
2808
- function __errorPage(status, detail) {
2809
- const title = __errorMessages[status] || 'Error';
2810
- const detailHtml = detail ? '<p style="font-family:ui-monospace,monospace;font-size:0.8rem;color:#555;background:#111;padding:0.5rem 1rem;border-radius:6px;max-width:480px;margin:1rem auto 0;word-break:break-word">' + __esc(detail) + '</p>' : '';
2811
- return '<div style="display:flex;align-items:center;justify-content:center;min-height:60vh;text-align:center;padding:2rem">'
2812
- + '<div>'
2813
- + '<p style="font-size:5rem;font-weight:700;margin:0;color:#333;line-height:1">' + status + '</p>'
2814
- + '<p style="font-size:1rem;color:#555;margin:0.5rem 0 0;letter-spacing:0.05em">' + __esc(title) + '</p>'
2815
- + detailHtml
2816
- + '</div>'
2817
- + '</div>';
2818
- }
2819
-
2820
- ${customErrorFunctions ? '// Custom error page overrides (user-created NNN.html)\n' + customErrorFunctions + '\n' : ''}
2821
- // Dispatch: use custom override if it exists, otherwise built-in default
2822
- const __customErrors = {${Array.from(opts.compiledErrorPages.keys()).map(s => ` ${s}: __error_${s}`).join(',')} };
2823
-
2824
- function __error(status, detail) {
2825
- if (__customErrors[status]) return __customErrors[status](detail);
2826
- return __errorPage(status, detail);
2827
- }
2828
-
2829
- ${opts.compiledComponents.length > 0 ? '// �"��"� Components �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�\n\n' + opts.compiledComponents.join('\n\n') + '\n' : ''}${migrationInit}${authInit}${authPluginInit}${doResolverInit}${doClassCode}
2830
- // �"��"� Route definitions �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2831
-
2832
- const routes = [
2833
- ${opts.compiledRoutes.join(',\n')}
2834
- ];
2835
-
2836
- for (let i = 0; i < routes.length; i++) __addRoute(routes[i].pattern, i);
2837
-
2838
- // �"��"� Response helpers �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2839
-
2840
- const __defaultSecHeaders = {
2841
- 'X-Content-Type-Options': 'nosniff',
2842
- 'X-Frame-Options': 'DENY',
2843
- 'Referrer-Policy': 'strict-origin-when-cross-origin',
2844
- };
2845
-
2846
- function __secHeaders(response) {
2847
- for (const [k, v] of Object.entries(__defaultSecHeaders)) {
2848
- if (!response.headers.has(k)) response.headers.set(k, v);
2849
- }
2850
- return response;
2851
- }
2852
-
2853
- function __attachCookies(response) {
2854
- const cookies = __getLocals().__setCookieHeaders;
2855
- if (cookies && cookies.length > 0) {
2856
- const newResponse = new Response(response.body, response);
2857
- for (const h of cookies) newResponse.headers.append('Set-Cookie', h);
2858
- return __secHeaders(newResponse);
2859
- }
2860
- return __secHeaders(response);
2861
- }
2862
-
2863
- function __isSameOrigin(request, url) {
2864
- const fetchSite = request.headers.get('sec-fetch-site');
2865
- if (fetchSite && fetchSite !== 'same-origin' && fetchSite !== 'same-site' && fetchSite !== 'none') {
2866
- return false;
2867
- }
2868
- const origin = request.headers.get('origin');
2869
- if (!origin) return true;
2870
- try { return new URL(origin).origin === url.origin; } catch { return false; }
2871
- }
2872
-
2873
- ${opts.isLayoutAsync ? 'async ' : ''}function __render(route, data) {
2874
- let html = route.render(data);
2875
- const headMatch = html.match(/<head>([\\s\\S]*?)<\\/head>/);
2876
- if (headMatch) {
2877
- html = html.replace(headMatch[0], '');
2878
- const layoutHtml = ${opts.isLayoutAsync ? 'await ' : ''}__layout(html);
2879
- return __attachCookies(new Response(layoutHtml.replace('</head>', headMatch[1] + '</head>'), {
2880
- headers: { 'content-type': 'text/html; charset=utf-8' }
2881
- }));
2882
- }
2883
- return __attachCookies(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(html), { headers: { 'content-type': 'text/html; charset=utf-8' } }));
2884
- }
2885
-
2886
- const __runtimeDef = (typeof __kuratchiRuntime !== 'undefined' && __kuratchiRuntime && typeof __kuratchiRuntime === 'object') ? __kuratchiRuntime : {};
2887
- const __runtimeEntries = Object.entries(__runtimeDef).filter(([, step]) => step && typeof step === 'object');
2888
-
2889
- async function __runRuntimeRequest(ctx, next) {
2890
- let idx = -1;
2891
- async function __dispatch(i) {
2892
- if (i <= idx) throw new Error('[kuratchi runtime] next() called multiple times in request phase');
2893
- idx = i;
2894
- const entry = __runtimeEntries[i];
2895
- if (!entry) return next();
2896
- const [, step] = entry;
2897
- if (typeof step.request !== 'function') return __dispatch(i + 1);
2898
- return await step.request(ctx, () => __dispatch(i + 1));
2899
- }
2900
- return __dispatch(0);
2901
- }
2902
-
2903
- async function __runRuntimeRoute(ctx, next) {
2904
- let idx = -1;
2905
- async function __dispatch(i) {
2906
- if (i <= idx) throw new Error('[kuratchi runtime] next() called multiple times in route phase');
2907
- idx = i;
2908
- const entry = __runtimeEntries[i];
2909
- if (!entry) return next();
2910
- const [, step] = entry;
2911
- if (typeof step.route !== 'function') return __dispatch(i + 1);
2912
- return await step.route(ctx, () => __dispatch(i + 1));
2913
- }
2914
- return __dispatch(0);
2915
- }
2916
-
2917
- async function __runRuntimeResponse(ctx, response) {
2918
- let out = response;
2919
- for (const [, step] of __runtimeEntries) {
2920
- if (typeof step.response !== 'function') continue;
2921
- out = await step.response(ctx, out);
2922
- if (!(out instanceof Response)) {
2923
- throw new Error('[kuratchi runtime] response handlers must return a Response');
2924
- }
2925
- }
2926
- return out;
2927
- }
2928
-
2929
- async function __runRuntimeError(ctx, error) {
2930
- for (const [name, step] of __runtimeEntries) {
2931
- if (typeof step.error !== 'function') continue;
2932
- try {
2933
- const handled = await step.error(ctx, error);
2934
- if (handled instanceof Response) return handled;
2935
- } catch (hookErr) {
2936
- console.error('[kuratchi runtime] error handler failed in step', name, hookErr);
2937
- }
2938
- }
2939
- return null;
2940
- }
2941
-
2942
- // �"��"� Exported Worker entrypoint �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2943
-
2944
- export default class extends WorkerEntrypoint {
2945
- async fetch(request) {
2946
- __setRequestContext(this.ctx, request, __env);
2947
- ${migrationInit ? ' await __runMigrations();\n' : ''}${authInit ? ' __initAuth(request);\n' : ''}${authPluginInit ? ' __initAuthPlugins();\n' : ''}${doResolverInit ? ' __initDoResolvers();\n' : ''}
2948
- const __runtimeCtx = {
2949
- request,
2950
- env: __env,
2951
- ctx: this.ctx,
2952
- url: new URL(request.url),
2953
- params: {},
2954
- locals: __getLocals(),
2955
- };
2956
-
2957
- const __coreFetch = async () => {
2958
- const request = __runtimeCtx.request;
2959
- const url = __runtimeCtx.url;
2960
- ${ac?.hasRateLimit ? '\n // Rate limiting - check before route handlers\n { const __rlRes = await __checkRL(); if (__rlRes) return __secHeaders(__rlRes); }\n' : ''}${ac?.hasTurnstile ? ' // Turnstile bot protection\n { const __tsRes = await __checkTS(); if (__tsRes) return __secHeaders(__tsRes); }\n' : ''}${ac?.hasGuards ? ' // Route guards - redirect if not authenticated\n { const __gRes = __checkGuard(); if (__gRes) return __secHeaders(__gRes); }\n' : ''}
2961
-
2962
- // Serve static assets from src/assets/
2963
- if (url.pathname.startsWith('${opts.assetsPrefix}')) {
2964
- const name = url.pathname.slice('${opts.assetsPrefix}'.length);
2965
- const asset = __assets[name];
2966
- if (asset) {
2967
- if (request.headers.get('if-none-match') === asset.etag) {
2968
- return new Response(null, { status: 304 });
2969
- }
2970
- return new Response(asset.content, {
2971
- headers: { 'content-type': asset.mime, 'cache-control': 'public, max-age=31536000, immutable', 'etag': asset.etag }
2972
- });
2973
- }
2974
- return __secHeaders(new Response('Not Found', { status: 404 }));
2975
- }
2976
-
2977
- const match = __match(url.pathname);
2978
-
2979
- if (!match) {
2980
- return __secHeaders(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(__error(404)), { status: 404, headers: { 'content-type': 'text/html; charset=utf-8' } }));
2981
- }
2982
-
2983
- __runtimeCtx.params = match.params;
2984
- const route = routes[match.index];
2985
- __setLocal('params', match.params);
2986
-
2987
- // API route: dispatch to method handler
2988
- if (route.__api) {
2989
- const method = request.method;
2990
- if (method === 'OPTIONS') {
2991
- const handler = route['OPTIONS'];
2992
- if (typeof handler === 'function') return __secHeaders(await handler(__runtimeCtx));
2993
- const allowed = ['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'].filter(m => typeof route[m] === 'function').join(', ');
2994
- return __secHeaders(new Response(null, { status: 204, headers: { 'Allow': allowed, 'Access-Control-Allow-Methods': allowed } }));
2995
- }
2996
- const handler = route[method];
2997
- if (typeof handler !== 'function') {
2998
- const allowed = ['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'].filter(m => typeof route[m] === 'function').join(', ');
2999
- return __secHeaders(new Response(JSON.stringify({ error: 'Method Not Allowed' }), { status: 405, headers: { 'content-type': 'application/json', 'Allow': allowed } }));
3000
- }
3001
- return __secHeaders(await handler(__runtimeCtx));
3002
- }
3003
-
3004
- const __qFn = request.headers.get('x-kuratchi-query-fn') || '';
3005
- const __qArgsRaw = request.headers.get('x-kuratchi-query-args') || '[]';
3006
- let __qArgs = [];
3007
- try {
3008
- const __parsed = JSON.parse(__qArgsRaw);
3009
- __qArgs = Array.isArray(__parsed) ? __parsed : [];
3010
- } catch {}
3011
- __setLocal('__queryOverride', __qFn ? { fn: __qFn, args: __qArgs } : null);
3012
- if (!__getLocals().__breadcrumbs) {
3013
- __setLocal('breadcrumbs', __buildDefaultBreadcrumbs(url.pathname, match.params));
3014
- }
3015
-
3016
- // RPC call: GET ?_rpc=fnName&_args=[...] -> JSON response
3017
- const __rpcName = url.searchParams.get('_rpc');
3018
- if (request.method === 'GET' && __rpcName && route.rpc && Object.hasOwn(route.rpc, __rpcName)) {
3019
- if (request.headers.get('x-kuratchi-rpc') !== '1') {
3020
- return __secHeaders(new Response(JSON.stringify({ ok: false, error: 'Forbidden' }), {
3021
- status: 403, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
3022
- }));
3023
- }
3024
- try {
3025
- const __rpcArgsStr = url.searchParams.get('_args');
3026
- let __rpcArgs = [];
3027
- if (__rpcArgsStr) {
3028
- const __parsed = JSON.parse(__rpcArgsStr);
3029
- __rpcArgs = Array.isArray(__parsed) ? __parsed : [];
3030
- }
3031
- const __rpcResult = await route.rpc[__rpcName](...__rpcArgs);
3032
- return __secHeaders(new Response(JSON.stringify({ ok: true, data: __rpcResult }), {
3033
- headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
3034
- }));
3035
- } catch (err) {
3036
- console.error('[kuratchi] RPC error:', err);
3037
- const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' ? err.message : 'Internal Server Error';
3038
- return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
3039
- status: 500, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
3040
- }));
3041
- }
3042
- }
3043
-
3044
- // Form action: POST with hidden _action field in form body
3045
- if (request.method === 'POST') {
3046
- if (!__isSameOrigin(request, url)) {
3047
- return __secHeaders(new Response('Forbidden', { status: 403 }));
3048
- }
3049
- const formData = await request.formData();
3050
- const actionName = formData.get('_action');
3051
- const __actionFn = (actionName && route.actions && Object.hasOwn(route.actions, actionName) ? route.actions[actionName] : null)
3052
- || (actionName && __layoutActions && Object.hasOwn(__layoutActions, actionName) ? __layoutActions[actionName] : null);
3053
- if (actionName && __actionFn) {
3054
- // Check if this is a fetch-based action call (onclick) with JSON args
3055
- const argsStr = formData.get('_args');
3056
- const isFetchAction = argsStr !== null;
3057
- try {
3058
- if (isFetchAction) {
3059
- const __parsed = JSON.parse(argsStr);
3060
- const args = Array.isArray(__parsed) ? __parsed : [];
3061
- await __actionFn(...args);
3062
- } else {
3063
- await __actionFn(formData);
3064
- }
3065
- } catch (err) {
3066
- if (err && err.isRedirectError) {
3067
- const __redirectTo = err.location || url.pathname;
3068
- const __redirectStatus = Number(err.status) || 303;
3069
- if (isFetchAction) {
3070
- return __attachCookies(__secHeaders(new Response(JSON.stringify({ ok: true, redirectTo: __redirectTo, redirectStatus: __redirectStatus }), {
3071
- headers: { 'content-type': 'application/json' }
3072
- })));
3073
- }
3074
- return __attachCookies(new Response(null, { status: __redirectStatus, headers: { 'location': __redirectTo } }));
3075
- }
3076
- console.error('[kuratchi] Action error:', err);
3077
- if (isFetchAction) {
3078
- const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' && err && err.message ? err.message : 'Internal Server Error';
3079
- return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
3080
- status: 500, headers: { 'content-type': 'application/json' }
3081
- }));
3082
- }
3083
- const __loaded = route.load ? await route.load(match.params) : {};
3084
- const data = (__loaded && typeof __loaded === 'object') ? __loaded : { value: __loaded };
3085
- data.params = match.params;
3086
- data.breadcrumbs = __getLocals().__breadcrumbs ?? [];
3087
- const __allActions = Object.assign({}, route.actions, __layoutActions || {});
3088
- Object.keys(__allActions).forEach(function(k) { if (!(k in data)) data[k] = { error: undefined, loading: false, success: false }; });
3089
- const __errMsg = (err && err.isActionError) ? err.message : (typeof __kuratchi_DEV__ !== 'undefined' && err && err.message) ? err.message : 'Action failed';
3090
- data[actionName] = { error: __errMsg, loading: false, success: false };
3091
- return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
3092
- }
3093
- // Fetch-based actions return lightweight JSON (no page re-render)
3094
- if (isFetchAction) {
3095
- return __attachCookies(new Response(JSON.stringify({ ok: true }), {
3096
- headers: { 'content-type': 'application/json' }
3097
- }));
3098
- }
3099
- // POST-Redirect-GET: redirect to custom target or back to same URL
3100
- const __locals = __getLocals();
3101
- const redirectTo = __locals.__redirectTo || url.pathname;
3102
- const redirectStatus = Number(__locals.__redirectStatus) || 303;
3103
- return __attachCookies(new Response(null, { status: redirectStatus, headers: { 'location': redirectTo } }));
3104
- }
3105
- }
3106
-
3107
- // GET (or unmatched POST): load + render
3108
- try {
3109
- const __loaded = route.load ? await route.load(match.params) : {};
3110
- const data = (__loaded && typeof __loaded === 'object') ? __loaded : { value: __loaded };
3111
- data.params = match.params;
3112
- data.breadcrumbs = __getLocals().__breadcrumbs ?? [];
3113
- const __allActionsGet = Object.assign({}, route.actions, __layoutActions || {});
3114
- Object.keys(__allActionsGet).forEach(function(k) { if (!(k in data)) data[k] = { error: undefined, loading: false, success: false }; });
3115
- return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
3116
- } catch (err) {
3117
- if (err && err.isRedirectError) {
3118
- const __redirectTo = err.location || url.pathname;
3119
- const __redirectStatus = Number(err.status) || 303;
3120
- return __attachCookies(new Response(null, { status: __redirectStatus, headers: { 'location': __redirectTo } }));
3121
- }
3122
- console.error('[kuratchi] Route load/render error:', err);
3123
- const __pageErrStatus = (err && err.isPageError && err.status) ? err.status : 500;
3124
- const __errDetail = (err && err.isPageError) ? err.message : (typeof __kuratchi_DEV__ !== 'undefined' && err && err.message) ? err.message : undefined;
3125
- return __secHeaders(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(__error(__pageErrStatus, __errDetail)), { status: __pageErrStatus, headers: { 'content-type': 'text/html; charset=utf-8' } }));
3126
- }
3127
- };
3128
-
3129
- try {
3130
- const __requestResponse = await __runRuntimeRequest(__runtimeCtx, async () => {
3131
- return __runRuntimeRoute(__runtimeCtx, __coreFetch);
3132
- });
3133
- return await __runRuntimeResponse(__runtimeCtx, __requestResponse);
3134
- } catch (err) {
3135
- const __handled = await __runRuntimeError(__runtimeCtx, err);
3136
- if (__handled) return __secHeaders(__handled);
3137
- throw err;
3138
- }
3139
- }
3140
- }
2793
+ return `// Generated by KuratchiJS compiler �" do not edit.
2794
+ ${opts.isDev ? '\nglobalThis.__kuratchi_DEV__ = true;\n' : ''}
2795
+ ${workerImport}
2796
+ ${contextImport}
2797
+ ${runtimeImport ? runtimeImport + '\n' : ''}${migrationImports ? migrationImports + '\n' : ''}${authPluginImports ? authPluginImports + '\n' : ''}${doImports ? doImports + '\n' : ''}${opts.serverImports.join('\n')}
2798
+
2799
+ // �"��"� Assets �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2800
+
2801
+ const __assets = {
2802
+ ${opts.compiledAssets.map(a => ` ${JSON.stringify(a.name)}: { content: ${JSON.stringify(a.content)}, mime: ${JSON.stringify(a.mime)}, etag: ${JSON.stringify(a.etag)} }`).join(',\n')}
2803
+ };
2804
+
2805
+ // �"��"� Router �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2806
+
2807
+ const __staticRoutes = new Map(); // exact path �' index (O(1) lookup)
2808
+ const __dynamicRoutes = []; // regex-based routes (params/wildcards)
2809
+
2810
+ function __addRoute(pattern, index) {
2811
+ if (!pattern.includes(':') && !pattern.includes('*')) {
2812
+ // Static route �" direct Map lookup, no regex needed
2813
+ __staticRoutes.set(pattern, index);
2814
+ } else {
2815
+ // Dynamic route �" build regex for param extraction
2816
+ const paramNames = [];
2817
+ let regexStr = pattern
2818
+ .replace(/\\*(\\w+)/g, (_, name) => { paramNames.push(name); return '(?<' + name + '>.+)'; })
2819
+ .replace(/:(\\w+)/g, (_, name) => { paramNames.push(name); return '(?<' + name + '>[^/]+)'; });
2820
+ __dynamicRoutes.push({ regex: new RegExp('^' + regexStr + '$'), paramNames, index });
2821
+ }
2822
+ }
2823
+
2824
+ function __match(pathname) {
2825
+ const normalized = pathname === '/' ? '/' : pathname.replace(/\\/$/, '');
2826
+ // Fast path: static routes (most common)
2827
+ const staticIdx = __staticRoutes.get(normalized);
2828
+ if (staticIdx !== undefined) return { params: {}, index: staticIdx };
2829
+ // Slow path: dynamic routes with params
2830
+ for (const route of __dynamicRoutes) {
2831
+ const m = normalized.match(route.regex);
2832
+ if (m) {
2833
+ const params = {};
2834
+ for (const name of route.paramNames) params[name] = m.groups?.[name] ?? '';
2835
+ return { params, index: route.index };
2836
+ }
2837
+ }
2838
+ return null;
2839
+ }
2840
+
2841
+ // �"��"� Layout �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2842
+
2843
+ ${layoutBlock}
2844
+
2845
+ ${layoutActionsBlock}
2846
+
2847
+ // �"��"� Error pages �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2848
+
2849
+ const __errorMessages = {
2850
+ 400: 'Bad Request',
2851
+ 401: 'Unauthorized',
2852
+ 403: 'Forbidden',
2853
+ 404: 'Not Found',
2854
+ 405: 'Method Not Allowed',
2855
+ 408: 'Request Timeout',
2856
+ 429: 'Too Many Requests',
2857
+ 500: 'Internal Server Error',
2858
+ 502: 'Bad Gateway',
2859
+ 503: 'Service Unavailable',
2860
+ };
2861
+
2862
+ // Built-in default error page �" clean, dark, minimal, centered
2863
+ function __errorPage(status, detail) {
2864
+ const title = __errorMessages[status] || 'Error';
2865
+ const detailHtml = detail ? '<p style="font-family:ui-monospace,monospace;font-size:0.8rem;color:#555;background:#111;padding:0.5rem 1rem;border-radius:6px;max-width:480px;margin:1rem auto 0;word-break:break-word">' + __esc(detail) + '</p>' : '';
2866
+ return '<div style="display:flex;align-items:center;justify-content:center;min-height:60vh;text-align:center;padding:2rem">'
2867
+ + '<div>'
2868
+ + '<p style="font-size:5rem;font-weight:700;margin:0;color:#333;line-height:1">' + status + '</p>'
2869
+ + '<p style="font-size:1rem;color:#555;margin:0.5rem 0 0;letter-spacing:0.05em">' + __esc(title) + '</p>'
2870
+ + detailHtml
2871
+ + '</div>'
2872
+ + '</div>';
2873
+ }
2874
+
2875
+ ${customErrorFunctions ? '// Custom error page overrides (user-created NNN.html)\n' + customErrorFunctions + '\n' : ''}
2876
+ // Dispatch: use custom override if it exists, otherwise built-in default
2877
+ const __customErrors = {${Array.from(opts.compiledErrorPages.keys()).map(s => ` ${s}: __error_${s}`).join(',')} };
2878
+
2879
+ function __error(status, detail) {
2880
+ if (__customErrors[status]) return __customErrors[status](detail);
2881
+ return __errorPage(status, detail);
2882
+ }
2883
+
2884
+ ${opts.compiledComponents.length > 0 ? '// �"��"� Components �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�\n\n' + opts.compiledComponents.join('\n\n') + '\n' : ''}${migrationInit}${authInit}${authPluginInit}${doResolverInit}${doClassCode}
2885
+ // �"��"� Route definitions �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2886
+
2887
+ const routes = [
2888
+ ${opts.compiledRoutes.join(',\n')}
2889
+ ];
2890
+
2891
+ for (let i = 0; i < routes.length; i++) __addRoute(routes[i].pattern, i);
2892
+
2893
+ // �"��"� Response helpers �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2894
+
2895
+ const __defaultSecHeaders = {
2896
+ 'X-Content-Type-Options': 'nosniff',
2897
+ 'X-Frame-Options': 'DENY',
2898
+ 'Referrer-Policy': 'strict-origin-when-cross-origin',
2899
+ };
2900
+
2901
+ function __secHeaders(response) {
2902
+ for (const [k, v] of Object.entries(__defaultSecHeaders)) {
2903
+ if (!response.headers.has(k)) response.headers.set(k, v);
2904
+ }
2905
+ return response;
2906
+ }
2907
+
2908
+ function __attachCookies(response) {
2909
+ const cookies = __getLocals().__setCookieHeaders;
2910
+ if (cookies && cookies.length > 0) {
2911
+ const newResponse = new Response(response.body, response);
2912
+ for (const h of cookies) newResponse.headers.append('Set-Cookie', h);
2913
+ return __secHeaders(newResponse);
2914
+ }
2915
+ return __secHeaders(response);
2916
+ }
2917
+
2918
+ function __isSameOrigin(request, url) {
2919
+ const fetchSite = request.headers.get('sec-fetch-site');
2920
+ if (fetchSite && fetchSite !== 'same-origin' && fetchSite !== 'same-site' && fetchSite !== 'none') {
2921
+ return false;
2922
+ }
2923
+ const origin = request.headers.get('origin');
2924
+ if (!origin) return true;
2925
+ try { return new URL(origin).origin === url.origin; } catch { return false; }
2926
+ }
2927
+
2928
+ ${opts.isLayoutAsync ? 'async ' : ''}function __render(route, data) {
2929
+ let html = route.render(data);
2930
+ const headMatch = html.match(/<head>([\\s\\S]*?)<\\/head>/);
2931
+ if (headMatch) {
2932
+ html = html.replace(headMatch[0], '');
2933
+ const layoutHtml = ${opts.isLayoutAsync ? 'await ' : ''}__layout(html);
2934
+ return __attachCookies(new Response(layoutHtml.replace('</head>', headMatch[1] + '</head>'), {
2935
+ headers: { 'content-type': 'text/html; charset=utf-8' }
2936
+ }));
2937
+ }
2938
+ return __attachCookies(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(html), { headers: { 'content-type': 'text/html; charset=utf-8' } }));
2939
+ }
2940
+
2941
+ const __runtimeDef = (typeof __kuratchiRuntime !== 'undefined' && __kuratchiRuntime && typeof __kuratchiRuntime === 'object') ? __kuratchiRuntime : {};
2942
+ const __runtimeEntries = Object.entries(__runtimeDef).filter(([, step]) => step && typeof step === 'object');
2943
+
2944
+ async function __runRuntimeRequest(ctx, next) {
2945
+ let idx = -1;
2946
+ async function __dispatch(i) {
2947
+ if (i <= idx) throw new Error('[kuratchi runtime] next() called multiple times in request phase');
2948
+ idx = i;
2949
+ const entry = __runtimeEntries[i];
2950
+ if (!entry) return next();
2951
+ const [, step] = entry;
2952
+ if (typeof step.request !== 'function') return __dispatch(i + 1);
2953
+ return await step.request(ctx, () => __dispatch(i + 1));
2954
+ }
2955
+ return __dispatch(0);
2956
+ }
2957
+
2958
+ async function __runRuntimeRoute(ctx, next) {
2959
+ let idx = -1;
2960
+ async function __dispatch(i) {
2961
+ if (i <= idx) throw new Error('[kuratchi runtime] next() called multiple times in route phase');
2962
+ idx = i;
2963
+ const entry = __runtimeEntries[i];
2964
+ if (!entry) return next();
2965
+ const [, step] = entry;
2966
+ if (typeof step.route !== 'function') return __dispatch(i + 1);
2967
+ return await step.route(ctx, () => __dispatch(i + 1));
2968
+ }
2969
+ return __dispatch(0);
2970
+ }
2971
+
2972
+ async function __runRuntimeResponse(ctx, response) {
2973
+ let out = response;
2974
+ for (const [, step] of __runtimeEntries) {
2975
+ if (typeof step.response !== 'function') continue;
2976
+ out = await step.response(ctx, out);
2977
+ if (!(out instanceof Response)) {
2978
+ throw new Error('[kuratchi runtime] response handlers must return a Response');
2979
+ }
2980
+ }
2981
+ return out;
2982
+ }
2983
+
2984
+ async function __runRuntimeError(ctx, error) {
2985
+ for (const [name, step] of __runtimeEntries) {
2986
+ if (typeof step.error !== 'function') continue;
2987
+ try {
2988
+ const handled = await step.error(ctx, error);
2989
+ if (handled instanceof Response) return handled;
2990
+ } catch (hookErr) {
2991
+ console.error('[kuratchi runtime] error handler failed in step', name, hookErr);
2992
+ }
2993
+ }
2994
+ return null;
2995
+ }
2996
+
2997
+ // �"��"� Exported Worker entrypoint �"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"��"�
2998
+
2999
+ export default class extends WorkerEntrypoint {
3000
+ async fetch(request) {
3001
+ __setRequestContext(this.ctx, request, __env);
3002
+ ${migrationInit ? ' await __runMigrations();\n' : ''}${authInit ? ' __initAuth(request);\n' : ''}${authPluginInit ? ' __initAuthPlugins();\n' : ''}${doResolverInit ? ' __initDoResolvers();\n' : ''}
3003
+ const __runtimeCtx = {
3004
+ request,
3005
+ env: __env,
3006
+ ctx: this.ctx,
3007
+ url: new URL(request.url),
3008
+ params: {},
3009
+ locals: __getLocals(),
3010
+ };
3011
+
3012
+ const __coreFetch = async () => {
3013
+ const request = __runtimeCtx.request;
3014
+ const url = __runtimeCtx.url;
3015
+ ${ac?.hasRateLimit ? '\n // Rate limiting - check before route handlers\n { const __rlRes = await __checkRL(); if (__rlRes) return __secHeaders(__rlRes); }\n' : ''}${ac?.hasTurnstile ? ' // Turnstile bot protection\n { const __tsRes = await __checkTS(); if (__tsRes) return __secHeaders(__tsRes); }\n' : ''}${ac?.hasGuards ? ' // Route guards - redirect if not authenticated\n { const __gRes = __checkGuard(); if (__gRes) return __secHeaders(__gRes); }\n' : ''}
3016
+
3017
+ // Serve static assets from src/assets/
3018
+ if (url.pathname.startsWith('${opts.assetsPrefix}')) {
3019
+ const name = url.pathname.slice('${opts.assetsPrefix}'.length);
3020
+ const asset = __assets[name];
3021
+ if (asset) {
3022
+ if (request.headers.get('if-none-match') === asset.etag) {
3023
+ return new Response(null, { status: 304 });
3024
+ }
3025
+ return new Response(asset.content, {
3026
+ headers: { 'content-type': asset.mime, 'cache-control': 'public, max-age=31536000, immutable', 'etag': asset.etag }
3027
+ });
3028
+ }
3029
+ return __secHeaders(new Response('Not Found', { status: 404 }));
3030
+ }
3031
+
3032
+ const match = __match(url.pathname);
3033
+
3034
+ if (!match) {
3035
+ return __secHeaders(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(__error(404)), { status: 404, headers: { 'content-type': 'text/html; charset=utf-8' } }));
3036
+ }
3037
+
3038
+ __runtimeCtx.params = match.params;
3039
+ const route = routes[match.index];
3040
+ __setLocal('params', match.params);
3041
+
3042
+ // API route: dispatch to method handler
3043
+ if (route.__api) {
3044
+ const method = request.method;
3045
+ if (method === 'OPTIONS') {
3046
+ const handler = route['OPTIONS'];
3047
+ if (typeof handler === 'function') return __secHeaders(await handler(__runtimeCtx));
3048
+ const allowed = ['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'].filter(m => typeof route[m] === 'function').join(', ');
3049
+ return __secHeaders(new Response(null, { status: 204, headers: { 'Allow': allowed, 'Access-Control-Allow-Methods': allowed } }));
3050
+ }
3051
+ const handler = route[method];
3052
+ if (typeof handler !== 'function') {
3053
+ const allowed = ['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'].filter(m => typeof route[m] === 'function').join(', ');
3054
+ return __secHeaders(new Response(JSON.stringify({ error: 'Method Not Allowed' }), { status: 405, headers: { 'content-type': 'application/json', 'Allow': allowed } }));
3055
+ }
3056
+ return __secHeaders(await handler(__runtimeCtx));
3057
+ }
3058
+
3059
+ const __qFn = request.headers.get('x-kuratchi-query-fn') || '';
3060
+ const __qArgsRaw = request.headers.get('x-kuratchi-query-args') || '[]';
3061
+ let __qArgs = [];
3062
+ try {
3063
+ const __parsed = JSON.parse(__qArgsRaw);
3064
+ __qArgs = Array.isArray(__parsed) ? __parsed : [];
3065
+ } catch {}
3066
+ __setLocal('__queryOverride', __qFn ? { fn: __qFn, args: __qArgs } : null);
3067
+ if (!__getLocals().__breadcrumbs) {
3068
+ __setLocal('breadcrumbs', __buildDefaultBreadcrumbs(url.pathname, match.params));
3069
+ }
3070
+
3071
+ // RPC call: GET ?_rpc=fnName&_args=[...] -> JSON response
3072
+ const __rpcName = url.searchParams.get('_rpc');
3073
+ if (request.method === 'GET' && __rpcName && route.rpc && Object.hasOwn(route.rpc, __rpcName)) {
3074
+ if (request.headers.get('x-kuratchi-rpc') !== '1') {
3075
+ return __secHeaders(new Response(JSON.stringify({ ok: false, error: 'Forbidden' }), {
3076
+ status: 403, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
3077
+ }));
3078
+ }
3079
+ try {
3080
+ const __rpcArgsStr = url.searchParams.get('_args');
3081
+ let __rpcArgs = [];
3082
+ if (__rpcArgsStr) {
3083
+ const __parsed = JSON.parse(__rpcArgsStr);
3084
+ __rpcArgs = Array.isArray(__parsed) ? __parsed : [];
3085
+ }
3086
+ const __rpcResult = await route.rpc[__rpcName](...__rpcArgs);
3087
+ return __secHeaders(new Response(JSON.stringify({ ok: true, data: __rpcResult }), {
3088
+ headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
3089
+ }));
3090
+ } catch (err) {
3091
+ console.error('[kuratchi] RPC error:', err);
3092
+ const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' ? err.message : 'Internal Server Error';
3093
+ return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
3094
+ status: 500, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
3095
+ }));
3096
+ }
3097
+ }
3098
+
3099
+ // Form action: POST with hidden _action field in form body
3100
+ if (request.method === 'POST') {
3101
+ if (!__isSameOrigin(request, url)) {
3102
+ return __secHeaders(new Response('Forbidden', { status: 403 }));
3103
+ }
3104
+ const formData = await request.formData();
3105
+ const actionName = formData.get('_action');
3106
+ const __actionFn = (actionName && route.actions && Object.hasOwn(route.actions, actionName) ? route.actions[actionName] : null)
3107
+ || (actionName && __layoutActions && Object.hasOwn(__layoutActions, actionName) ? __layoutActions[actionName] : null);
3108
+ if (actionName && __actionFn) {
3109
+ // Check if this is a fetch-based action call (onclick) with JSON args
3110
+ const argsStr = formData.get('_args');
3111
+ const isFetchAction = argsStr !== null;
3112
+ try {
3113
+ if (isFetchAction) {
3114
+ const __parsed = JSON.parse(argsStr);
3115
+ const args = Array.isArray(__parsed) ? __parsed : [];
3116
+ await __actionFn(...args);
3117
+ } else {
3118
+ await __actionFn(formData);
3119
+ }
3120
+ } catch (err) {
3121
+ if (err && err.isRedirectError) {
3122
+ const __redirectTo = err.location || url.pathname;
3123
+ const __redirectStatus = Number(err.status) || 303;
3124
+ if (isFetchAction) {
3125
+ return __attachCookies(__secHeaders(new Response(JSON.stringify({ ok: true, redirectTo: __redirectTo, redirectStatus: __redirectStatus }), {
3126
+ headers: { 'content-type': 'application/json' }
3127
+ })));
3128
+ }
3129
+ return __attachCookies(new Response(null, { status: __redirectStatus, headers: { 'location': __redirectTo } }));
3130
+ }
3131
+ console.error('[kuratchi] Action error:', err);
3132
+ if (isFetchAction) {
3133
+ const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' && err && err.message ? err.message : 'Internal Server Error';
3134
+ return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
3135
+ status: 500, headers: { 'content-type': 'application/json' }
3136
+ }));
3137
+ }
3138
+ const __loaded = route.load ? await route.load(match.params) : {};
3139
+ const data = (__loaded && typeof __loaded === 'object') ? __loaded : { value: __loaded };
3140
+ data.params = match.params;
3141
+ data.breadcrumbs = __getLocals().__breadcrumbs ?? [];
3142
+ const __allActions = Object.assign({}, route.actions, __layoutActions || {});
3143
+ Object.keys(__allActions).forEach(function(k) { if (!(k in data)) data[k] = { error: undefined, loading: false, success: false }; });
3144
+ const __errMsg = (err && err.isActionError) ? err.message : (typeof __kuratchi_DEV__ !== 'undefined' && err && err.message) ? err.message : 'Action failed';
3145
+ data[actionName] = { error: __errMsg, loading: false, success: false };
3146
+ return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
3147
+ }
3148
+ // Fetch-based actions return lightweight JSON (no page re-render)
3149
+ if (isFetchAction) {
3150
+ return __attachCookies(new Response(JSON.stringify({ ok: true }), {
3151
+ headers: { 'content-type': 'application/json' }
3152
+ }));
3153
+ }
3154
+ // POST-Redirect-GET: redirect to custom target or back to same URL
3155
+ const __locals = __getLocals();
3156
+ const redirectTo = __locals.__redirectTo || url.pathname;
3157
+ const redirectStatus = Number(__locals.__redirectStatus) || 303;
3158
+ return __attachCookies(new Response(null, { status: redirectStatus, headers: { 'location': redirectTo } }));
3159
+ }
3160
+ }
3161
+
3162
+ // GET (or unmatched POST): load + render
3163
+ try {
3164
+ const __loaded = route.load ? await route.load(match.params) : {};
3165
+ const data = (__loaded && typeof __loaded === 'object') ? __loaded : { value: __loaded };
3166
+ data.params = match.params;
3167
+ data.breadcrumbs = __getLocals().__breadcrumbs ?? [];
3168
+ const __allActionsGet = Object.assign({}, route.actions, __layoutActions || {});
3169
+ Object.keys(__allActionsGet).forEach(function(k) { if (!(k in data)) data[k] = { error: undefined, loading: false, success: false }; });
3170
+ return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
3171
+ } catch (err) {
3172
+ if (err && err.isRedirectError) {
3173
+ const __redirectTo = err.location || url.pathname;
3174
+ const __redirectStatus = Number(err.status) || 303;
3175
+ return __attachCookies(new Response(null, { status: __redirectStatus, headers: { 'location': __redirectTo } }));
3176
+ }
3177
+ console.error('[kuratchi] Route load/render error:', err);
3178
+ const __pageErrStatus = (err && err.isPageError && err.status) ? err.status : 500;
3179
+ const __errDetail = (err && err.isPageError) ? err.message : (typeof __kuratchi_DEV__ !== 'undefined' && err && err.message) ? err.message : undefined;
3180
+ return __secHeaders(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(__error(__pageErrStatus, __errDetail)), { status: __pageErrStatus, headers: { 'content-type': 'text/html; charset=utf-8' } }));
3181
+ }
3182
+ };
3183
+
3184
+ try {
3185
+ const __requestResponse = await __runRuntimeRequest(__runtimeCtx, async () => {
3186
+ return __runRuntimeRoute(__runtimeCtx, __coreFetch);
3187
+ });
3188
+ return await __runRuntimeResponse(__runtimeCtx, __requestResponse);
3189
+ } catch (err) {
3190
+ const __handled = await __runRuntimeError(__runtimeCtx, err);
3191
+ if (__handled) return __secHeaders(__handled);
3192
+ throw err;
3193
+ }
3194
+ }
3195
+ }
3141
3196
  `;
3142
3197
  }
3143
3198
  function resolveRuntimeImportPath(projectDir) {
@@ -3158,3 +3213,216 @@ function toWorkerImportPath(projectDir, outDir, filePath) {
3158
3213
  rel = `./${rel}`;
3159
3214
  return rel.replace(/\.(ts|js|mjs|cjs)$/, '');
3160
3215
  }
3216
+ /**
3217
+ * Auto-sync wrangler.jsonc with workflow/container/DO config from kuratchi.config.ts.
3218
+ * This eliminates the need to manually duplicate config between kuratchi.config.ts and wrangler.jsonc.
3219
+ *
3220
+ * The function:
3221
+ * 1. Reads existing wrangler.jsonc (or wrangler.json)
3222
+ * 2. Updates/adds workflow entries based on kuratchi.config.ts
3223
+ * 3. Preserves all other wrangler config (bindings, vars, etc.)
3224
+ * 4. Writes back only if changed
3225
+ */
3226
+ function syncWranglerConfig(projectDir, config) {
3227
+ // Find wrangler config file (prefer .jsonc, fall back to .json)
3228
+ const jsoncPath = path.join(projectDir, 'wrangler.jsonc');
3229
+ const jsonPath = path.join(projectDir, 'wrangler.json');
3230
+ const tomlPath = path.join(projectDir, 'wrangler.toml');
3231
+ let configPath;
3232
+ let isJsonc = false;
3233
+ if (fs.existsSync(jsoncPath)) {
3234
+ configPath = jsoncPath;
3235
+ isJsonc = true;
3236
+ }
3237
+ else if (fs.existsSync(jsonPath)) {
3238
+ configPath = jsonPath;
3239
+ }
3240
+ else if (fs.existsSync(tomlPath)) {
3241
+ // TOML is not supported for auto-sync — user must migrate to JSON/JSONC
3242
+ console.log('[kuratchi] wrangler.toml detected. Auto-sync requires wrangler.jsonc. Skipping wrangler sync.');
3243
+ return;
3244
+ }
3245
+ else {
3246
+ // No wrangler config exists — create a minimal wrangler.jsonc
3247
+ console.log('[kuratchi] Creating wrangler.jsonc with workflow config...');
3248
+ configPath = jsoncPath;
3249
+ isJsonc = true;
3250
+ }
3251
+ // Read existing config (or start fresh)
3252
+ let rawContent = '';
3253
+ let wranglerConfig = {};
3254
+ if (fs.existsSync(configPath)) {
3255
+ rawContent = fs.readFileSync(configPath, 'utf-8');
3256
+ try {
3257
+ // Strip JSONC comments for parsing
3258
+ const jsonContent = stripJsonComments(rawContent);
3259
+ wranglerConfig = JSON.parse(jsonContent);
3260
+ }
3261
+ catch (err) {
3262
+ console.error(`[kuratchi] Failed to parse ${path.basename(configPath)}: ${err.message}`);
3263
+ console.error('[kuratchi] Skipping wrangler sync. Please fix the JSON syntax.');
3264
+ return;
3265
+ }
3266
+ }
3267
+ let changed = false;
3268
+ // Sync workflows
3269
+ if (config.workflows.length > 0) {
3270
+ const existingWorkflows = wranglerConfig.workflows || [];
3271
+ const existingByBinding = new Map(existingWorkflows.map(w => [w.binding, w]));
3272
+ for (const wf of config.workflows) {
3273
+ // Convert SCREAMING_SNAKE binding to kebab-case name
3274
+ const name = wf.binding.toLowerCase().replace(/_/g, '-');
3275
+ const entry = {
3276
+ name,
3277
+ binding: wf.binding,
3278
+ class_name: wf.className,
3279
+ };
3280
+ const existing = existingByBinding.get(wf.binding);
3281
+ if (!existing) {
3282
+ existingWorkflows.push(entry);
3283
+ changed = true;
3284
+ console.log(`[kuratchi] Added workflow "${wf.binding}" to wrangler config`);
3285
+ }
3286
+ else if (existing.class_name !== wf.className) {
3287
+ existing.class_name = wf.className;
3288
+ changed = true;
3289
+ console.log(`[kuratchi] Updated workflow "${wf.binding}" class_name to "${wf.className}"`);
3290
+ }
3291
+ }
3292
+ // Remove workflows that are no longer in kuratchi.config.ts
3293
+ const configBindings = new Set(config.workflows.map(w => w.binding));
3294
+ const filtered = existingWorkflows.filter(w => {
3295
+ if (!configBindings.has(w.binding)) {
3296
+ // Check if this was a kuratchi-managed workflow (has matching naming convention)
3297
+ const expectedName = w.binding.toLowerCase().replace(/_/g, '-');
3298
+ if (w.name === expectedName) {
3299
+ console.log(`[kuratchi] Removed workflow "${w.binding}" from wrangler config`);
3300
+ changed = true;
3301
+ return false;
3302
+ }
3303
+ }
3304
+ return true;
3305
+ });
3306
+ if (filtered.length !== existingWorkflows.length) {
3307
+ wranglerConfig.workflows = filtered;
3308
+ }
3309
+ else {
3310
+ wranglerConfig.workflows = existingWorkflows;
3311
+ }
3312
+ if (wranglerConfig.workflows.length === 0) {
3313
+ delete wranglerConfig.workflows;
3314
+ }
3315
+ }
3316
+ // Sync containers (similar pattern)
3317
+ if (config.containers.length > 0) {
3318
+ const existingContainers = wranglerConfig.containers || [];
3319
+ const existingByBinding = new Map(existingContainers.map(c => [c.binding, c]));
3320
+ for (const ct of config.containers) {
3321
+ const name = ct.binding.toLowerCase().replace(/_/g, '-');
3322
+ const entry = {
3323
+ name,
3324
+ binding: ct.binding,
3325
+ class_name: ct.className,
3326
+ };
3327
+ const existing = existingByBinding.get(ct.binding);
3328
+ if (!existing) {
3329
+ existingContainers.push(entry);
3330
+ changed = true;
3331
+ console.log(`[kuratchi] Added container "${ct.binding}" to wrangler config`);
3332
+ }
3333
+ else if (existing.class_name !== ct.className) {
3334
+ existing.class_name = ct.className;
3335
+ changed = true;
3336
+ console.log(`[kuratchi] Updated container "${ct.binding}" class_name to "${ct.className}"`);
3337
+ }
3338
+ }
3339
+ wranglerConfig.containers = existingContainers;
3340
+ if (wranglerConfig.containers.length === 0) {
3341
+ delete wranglerConfig.containers;
3342
+ }
3343
+ }
3344
+ // Sync durable_objects
3345
+ if (config.durableObjects.length > 0) {
3346
+ if (!wranglerConfig.durable_objects) {
3347
+ wranglerConfig.durable_objects = { bindings: [] };
3348
+ }
3349
+ const existingBindings = wranglerConfig.durable_objects.bindings || [];
3350
+ const existingByName = new Map(existingBindings.map(b => [b.name, b]));
3351
+ for (const doEntry of config.durableObjects) {
3352
+ const entry = {
3353
+ name: doEntry.binding,
3354
+ class_name: doEntry.className,
3355
+ };
3356
+ const existing = existingByName.get(doEntry.binding);
3357
+ if (!existing) {
3358
+ existingBindings.push(entry);
3359
+ changed = true;
3360
+ console.log(`[kuratchi] Added durable_object "${doEntry.binding}" to wrangler config`);
3361
+ }
3362
+ else if (existing.class_name !== doEntry.className) {
3363
+ existing.class_name = doEntry.className;
3364
+ changed = true;
3365
+ console.log(`[kuratchi] Updated durable_object "${doEntry.binding}" class_name to "${doEntry.className}"`);
3366
+ }
3367
+ }
3368
+ wranglerConfig.durable_objects.bindings = existingBindings;
3369
+ }
3370
+ if (!changed)
3371
+ return;
3372
+ // Write back with pretty formatting
3373
+ const newContent = JSON.stringify(wranglerConfig, null, '\t');
3374
+ writeIfChanged(configPath, newContent + '\n');
3375
+ }
3376
+ /**
3377
+ * Strip JSON comments (// and /* *\/) for parsing JSONC files.
3378
+ */
3379
+ function stripJsonComments(content) {
3380
+ let result = '';
3381
+ let i = 0;
3382
+ let inString = false;
3383
+ let stringChar = '';
3384
+ while (i < content.length) {
3385
+ const ch = content[i];
3386
+ const next = content[i + 1];
3387
+ // Handle string literals
3388
+ if (inString) {
3389
+ result += ch;
3390
+ if (ch === '\\' && i + 1 < content.length) {
3391
+ result += next;
3392
+ i += 2;
3393
+ continue;
3394
+ }
3395
+ if (ch === stringChar) {
3396
+ inString = false;
3397
+ }
3398
+ i++;
3399
+ continue;
3400
+ }
3401
+ // Start of string
3402
+ if (ch === '"' || ch === "'") {
3403
+ inString = true;
3404
+ stringChar = ch;
3405
+ result += ch;
3406
+ i++;
3407
+ continue;
3408
+ }
3409
+ // Line comment
3410
+ if (ch === '/' && next === '/') {
3411
+ // Skip until end of line
3412
+ while (i < content.length && content[i] !== '\n')
3413
+ i++;
3414
+ continue;
3415
+ }
3416
+ // Block comment
3417
+ if (ch === '/' && next === '*') {
3418
+ i += 2;
3419
+ while (i < content.length - 1 && !(content[i] === '*' && content[i + 1] === '/'))
3420
+ i++;
3421
+ i += 2; // Skip */
3422
+ continue;
3423
+ }
3424
+ result += ch;
3425
+ i++;
3426
+ }
3427
+ return result;
3428
+ }