aiplang 2.7.1 → 2.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/aiplang.js CHANGED
@@ -5,7 +5,7 @@ const fs = require('fs')
5
5
  const path = require('path')
6
6
  const http = require('http')
7
7
 
8
- const VERSION = '2.7.1'
8
+ const VERSION = '2.7.3'
9
9
  const RUNTIME_DIR = path.join(__dirname, '..', 'runtime')
10
10
  const cmd = process.argv[2]
11
11
  const args = process.argv.slice(3)
@@ -615,23 +615,28 @@ function parseBlock(line) {
615
615
  }
616
616
 
617
617
  // ── table ───────────────────────────────────────────────────
618
- if(line.startsWith('table ')) {
618
+ if(line.startsWith('table ') || line.startsWith('table{')) {
619
619
  const idx=line.indexOf('{');if(idx===-1) return null
620
- const binding=line.slice(6,idx).trim()
620
+ const start=line.startsWith('table{')?6:6
621
+ const binding=line.slice(start,idx).trim().replace(/^@/,'@')
621
622
  const content=line.slice(idx+1,line.lastIndexOf('}')).trim()
622
623
  const em=content.match(/edit\s+(PUT|PATCH)\s+(\S+)/), dm=content.match(/delete\s+(?:DELETE\s+)?(\S+)/)
623
624
  const clean=content.replace(/edit\s+(PUT|PATCH)\s+\S+/g,'').replace(/delete\s+(?:DELETE\s+)?\S+/g,'')
624
- return{kind:'table',binding,cols:parseCols(clean),empty:parseEmpty(clean),editPath:em?.[2]||null,editMethod:em?.[1]||'PUT',deletePath:dm?.[1]||null,deleteKey:'id',extraClass,animate}
625
+ const cols=parseCols(clean)
626
+ return{kind:'table',binding,cols:Array.isArray(cols)?cols:[],empty:parseEmpty(clean),editPath:em?.[2]||null,editMethod:em?.[1]||'PUT',deletePath:dm?.[1]||null,deleteKey:'id',extraClass,animate}
625
627
  }
626
628
 
627
629
  // ── form ────────────────────────────────────────────────────
628
- if(line.startsWith('form ')) {
630
+ if(line.startsWith('form ') || line.startsWith('form{')) {
629
631
  const bi=line.indexOf('{');if(bi===-1) return null
630
- let head=line.slice(5,bi).trim(); const content=line.slice(bi+1,line.lastIndexOf('}')).trim()
632
+ let head=line.slice(line.startsWith('form{')?4:5,bi).trim()
633
+ const content=line.slice(bi+1,line.lastIndexOf('}')).trim()
631
634
  let action=''; const ai=head.indexOf('=>')
632
635
  if(ai!==-1){action=head.slice(ai+2).trim();head=head.slice(0,ai).trim()}
633
- const [method,bpath]=head.split(/\s+/)
634
- return{kind:'form',method:method||'POST',bpath:bpath||'',action,fields:parseFields(content),extraClass,animate}
636
+ const parts=head.trim().split(/\s+/)
637
+ const method=parts[0]&&['GET','POST','PUT','PATCH','DELETE'].includes(parts[0].toUpperCase())?parts[0].toUpperCase():'POST'
638
+ const bpath=parts[method===parts[0].toUpperCase()?1:0]||''
639
+ return{kind:'form',method,bpath,action,fields:parseFields(content)||[],extraClass,animate}
635
640
  }
636
641
 
637
642
  // ── pricing ─────────────────────────────────────────────────
@@ -724,7 +729,7 @@ function applyMods(html, b) {
724
729
 
725
730
  function renderPage(page, allPages) {
726
731
  const needsJS=page.queries.length>0||page.blocks.some(b=>['table','list','form','if','btn','select','faq'].includes(b.kind))
727
- const body=page.blocks.map(b=>applyMods(renderBlock(b,page),b)).join('')
732
+ const body=page.blocks.map(b=>{try{return applyMods(renderBlock(b,page),b)}catch(e){console.error('[aiplang] Block render error:',b.kind,e.message);return ''}}).join('')
728
733
  const config=needsJS?JSON.stringify({id:page.id,theme:page.theme,routes:allPages.map(p=>p.route),state:page.state,queries:page.queries}):''
729
734
  const hydrate=needsJS?`\n<script>window.__AIPLANG_PAGE__=${config};</script>\n<script src="./aiplang-hydrate.js" defer></script>`:''
730
735
  const customVars=page.customTheme?genCustomThemeVars(page.customTheme):''
@@ -797,7 +802,7 @@ function rStats(b) {
797
802
  }
798
803
 
799
804
  function rRow(b) {
800
- const cards=b.items.map(item=>{
805
+ const cards=(b.items||[]).map(item=>{
801
806
  const inner=item.map((f,fi)=>{
802
807
  if(f.isImg) return`<img src="${esc(f.src)}" class="fx-card-img" alt="" loading="lazy">`
803
808
  if(f.isLink) return`<a href="${esc(f.path)}" class="fx-card-link">${esc(f.label)} →</a>`
@@ -822,7 +827,7 @@ function rSect(b) {
822
827
 
823
828
  function rFoot(b) {
824
829
  let inner=''
825
- for(const item of b.items) for(const f of item){
830
+ for(const item of (b.items||[])) for(const f of item){
826
831
  if(f.isLink) inner+=`<a href="${esc(f.path)}" class="fx-footer-link">${esc(f.label)}</a>`
827
832
  else inner+=`<p class="fx-footer-text">${esc(f.text)}</p>`
828
833
  }
@@ -830,24 +835,27 @@ function rFoot(b) {
830
835
  }
831
836
 
832
837
  function rTable(b) {
833
- const ths=b.cols.map(c=>`<th class="fx-th">${esc(c.label)}</th>`).join('')
834
- const keys=JSON.stringify(b.cols.map(c=>c.key))
835
- const cm=JSON.stringify(b.cols.map(c=>({label:c.label,key:c.key})))
838
+ const cols=Array.isArray(b.cols)?b.cols:[]
839
+ const ths=cols.map(c=>`<th class="fx-th">${esc(c.label)}</th>`).join('')
840
+ const keys=JSON.stringify(cols.map(c=>c.key))
841
+ const cm=JSON.stringify(cols.map(c=>({label:c.label,key:c.key})))
836
842
  const ea=b.editPath?` data-fx-edit="${esc(b.editPath)}" data-fx-edit-method="${esc(b.editMethod)}"`:''
837
843
  const da=b.deletePath?` data-fx-delete="${esc(b.deletePath)}"`:''
838
844
  const at=(b.editPath||b.deletePath)?'<th class="fx-th fx-th-actions">Actions</th>':''
839
- const span=b.cols.length+((b.editPath||b.deletePath)?1:0)
845
+ const span=cols.length+((b.editPath||b.deletePath)?1:0)
840
846
  return `<div class="fx-table-wrap"><table class="fx-table" data-fx-table="${esc(b.binding)}" data-fx-cols='${keys}' data-fx-col-map='${cm}'${ea}${da}><thead><tr>${ths}${at}</tr></thead><tbody class="fx-tbody"><tr><td colspan="${span}" class="fx-td-empty">${esc(b.empty)}</td></tr></tbody></table></div>\n`
841
847
  }
842
848
 
843
849
  function rForm(b) {
844
- const fields=b.fields.map(f=>{
850
+ const fields=(b.fields||[]).map(f=>{
851
+ if(!f) return ''
845
852
  const inp=f.type==='select'
846
853
  ?`<select class="fx-input" name="${esc(f.name)}"><option value="">Select...</option></select>`
847
- :`<input class="fx-input" type="${esc(f.type)}" name="${esc(f.name)}" placeholder="${esc(f.placeholder)}">`
854
+ :`<input class="fx-input" type="${esc(f.type||'text')}" name="${esc(f.name)}" placeholder="${esc(f.placeholder)}">`
848
855
  return`<div class="fx-field"><label class="fx-label">${esc(f.label)}</label>${inp}</div>`
849
856
  }).join('')
850
- return `<div class="fx-form-wrap"><form class="fx-form" data-fx-form="${esc(b.bpath)}" data-fx-method="${esc(b.method)}" data-fx-action="${esc(b.action)}">${fields}<div class="fx-form-msg"></div><button type="submit" class="fx-btn">Submit</button></form></div>\n`
857
+ const label=b.submitLabel||'Submit'
858
+ return `<div class="fx-form-wrap"><form class="fx-form" data-fx-form="${esc(b.bpath)}" data-fx-method="${esc(b.method)}" data-fx-action="${esc(b.action)}">${fields}<div class="fx-form-msg"></div><button type="submit" class="fx-btn">${esc(label)}</button></form></div>\n`
851
859
  }
852
860
 
853
861
  function rBtn(b) {
@@ -857,12 +865,12 @@ function rBtn(b) {
857
865
  }
858
866
 
859
867
  function rSelectBlock(b) {
860
- const opts=b.options.map(o=>`<option value="${esc(o)}">${esc(o)}</option>`).join('')
868
+ const opts=(b.options||[]).map(o=>`<option value="${esc(o)}">${esc(o)}</option>`).join('')
861
869
  return `<div class="fx-select-wrap"><select class="fx-input fx-select-block" data-fx-model="${esc(b.binding)}">${opts}</select></div>\n`
862
870
  }
863
871
 
864
872
  function rPricing(b) {
865
- const cards=b.plans.map((p,i)=>{
873
+ const cards=(b.plans||[]).map((p,i)=>{
866
874
  let lh='#',ll='Get started'
867
875
  if(p.linkRaw){const m=p.linkRaw.match(/\/([^:]+):(.+)/);if(m){lh='/'+m[1];ll=m[2]}}
868
876
  const f=i===1?' fx-pricing-featured':''
@@ -873,7 +881,7 @@ function rPricing(b) {
873
881
  }
874
882
 
875
883
  function rFaq(b) {
876
- const items=b.items.map(i=>`<div class="fx-faq-item" onclick="this.classList.toggle('open')"><div class="fx-faq-q">${esc(i.q)}<span class="fx-faq-arrow">▸</span></div><div class="fx-faq-a">${esc(i.a)}</div></div>`).join('')
884
+ const items=(b.items||[]).map(i=>`<div class="fx-faq-item" onclick="this.classList.toggle('open')"><div class="fx-faq-q">${esc(i.q)}<span class="fx-faq-arrow">▸</span></div><div class="fx-faq-a">${esc(i.a)}</div></div>`).join('')
877
885
  return `<section class="fx-sect"><div class="fx-faq">${items}</div></section>\n`
878
886
  }
879
887
 
@@ -883,7 +891,7 @@ function rTestimonial(b) {
883
891
  }
884
892
 
885
893
  function rGallery(b) {
886
- const imgs=b.imgs.map(src=>`<div class="fx-gallery-item"><img src="${esc(src)}" alt="" loading="lazy"></div>`).join('')
894
+ const imgs=(b.imgs||[]).map(src=>`<div class="fx-gallery-item"><img src="${esc(src)}" alt="" loading="lazy"></div>`).join('')
887
895
  return `<div class="fx-gallery">${imgs}</div>\n`
888
896
  }
889
897
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiplang",
3
- "version": "2.7.1",
3
+ "version": "2.7.3",
4
4
  "description": "AI-first web language. One .aip file = complete app. Frontend + backend + database + auth.",
5
5
  "keywords": [
6
6
  "aiplang",
package/server/server.js CHANGED
@@ -1435,7 +1435,7 @@ async function startServer(aipFile, port = 3000) {
1435
1435
 
1436
1436
  // Health
1437
1437
  srv.addRoute('GET', '/health', (req, res) => res.json(200, {
1438
- status:'ok', version:'2.7.1',
1438
+ status:'ok', version:'2.7.3',
1439
1439
  models: app.models.map(m=>m.name),
1440
1440
  routes: app.apis.length, pages: app.pages.length,
1441
1441
  admin: app.admin?.prefix || null,