aiplang 2.7.2 → 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.2'
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,13 +615,15 @@ 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 ────────────────────────────────────────────────────
@@ -727,7 +729,7 @@ function applyMods(html, b) {
727
729
 
728
730
  function renderPage(page, allPages) {
729
731
  const needsJS=page.queries.length>0||page.blocks.some(b=>['table','list','form','if','btn','select','faq'].includes(b.kind))
730
- 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('')
731
733
  const config=needsJS?JSON.stringify({id:page.id,theme:page.theme,routes:allPages.map(p=>p.route),state:page.state,queries:page.queries}):''
732
734
  const hydrate=needsJS?`\n<script>window.__AIPLANG_PAGE__=${config};</script>\n<script src="./aiplang-hydrate.js" defer></script>`:''
733
735
  const customVars=page.customTheme?genCustomThemeVars(page.customTheme):''
@@ -800,7 +802,7 @@ function rStats(b) {
800
802
  }
801
803
 
802
804
  function rRow(b) {
803
- const cards=b.items.map(item=>{
805
+ const cards=(b.items||[]).map(item=>{
804
806
  const inner=item.map((f,fi)=>{
805
807
  if(f.isImg) return`<img src="${esc(f.src)}" class="fx-card-img" alt="" loading="lazy">`
806
808
  if(f.isLink) return`<a href="${esc(f.path)}" class="fx-card-link">${esc(f.label)} →</a>`
@@ -825,7 +827,7 @@ function rSect(b) {
825
827
 
826
828
  function rFoot(b) {
827
829
  let inner=''
828
- for(const item of b.items) for(const f of item){
830
+ for(const item of (b.items||[])) for(const f of item){
829
831
  if(f.isLink) inner+=`<a href="${esc(f.path)}" class="fx-footer-link">${esc(f.label)}</a>`
830
832
  else inner+=`<p class="fx-footer-text">${esc(f.text)}</p>`
831
833
  }
@@ -833,13 +835,14 @@ function rFoot(b) {
833
835
  }
834
836
 
835
837
  function rTable(b) {
836
- const ths=b.cols.map(c=>`<th class="fx-th">${esc(c.label)}</th>`).join('')
837
- const keys=JSON.stringify(b.cols.map(c=>c.key))
838
- 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})))
839
842
  const ea=b.editPath?` data-fx-edit="${esc(b.editPath)}" data-fx-edit-method="${esc(b.editMethod)}"`:''
840
843
  const da=b.deletePath?` data-fx-delete="${esc(b.deletePath)}"`:''
841
844
  const at=(b.editPath||b.deletePath)?'<th class="fx-th fx-th-actions">Actions</th>':''
842
- const span=b.cols.length+((b.editPath||b.deletePath)?1:0)
845
+ const span=cols.length+((b.editPath||b.deletePath)?1:0)
843
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`
844
847
  }
845
848
 
@@ -862,12 +865,12 @@ function rBtn(b) {
862
865
  }
863
866
 
864
867
  function rSelectBlock(b) {
865
- 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('')
866
869
  return `<div class="fx-select-wrap"><select class="fx-input fx-select-block" data-fx-model="${esc(b.binding)}">${opts}</select></div>\n`
867
870
  }
868
871
 
869
872
  function rPricing(b) {
870
- const cards=b.plans.map((p,i)=>{
873
+ const cards=(b.plans||[]).map((p,i)=>{
871
874
  let lh='#',ll='Get started'
872
875
  if(p.linkRaw){const m=p.linkRaw.match(/\/([^:]+):(.+)/);if(m){lh='/'+m[1];ll=m[2]}}
873
876
  const f=i===1?' fx-pricing-featured':''
@@ -878,7 +881,7 @@ function rPricing(b) {
878
881
  }
879
882
 
880
883
  function rFaq(b) {
881
- 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('')
882
885
  return `<section class="fx-sect"><div class="fx-faq">${items}</div></section>\n`
883
886
  }
884
887
 
@@ -888,7 +891,7 @@ function rTestimonial(b) {
888
891
  }
889
892
 
890
893
  function rGallery(b) {
891
- 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('')
892
895
  return `<div class="fx-gallery">${imgs}</div>\n`
893
896
  }
894
897
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiplang",
3
- "version": "2.7.2",
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.2',
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,