FiveAI Fusion 语言
SpaceFive · Five 官方演示

一门语言。
一个文件。
整个 Web 都在这里。

SpaceFive 是一个用纯 Five 写成、开箱即用的 Web 框架。HTTP、WebSocket、PostgreSQL、bcrypt、AES、多语言,全都由运行时直接完成。不用 Node,不用 FFI,也不用 ORM 服务器。一个静态可执行文件直接对接 Postgres,上面跑着三个真实的应用——此刻正在运行。

—
在册资产
—
资产总值
—
在职员工
1
可执行文件
01 — 在线演示

三个应用, 一套框架,
一种质感。

你点到的每一处,背后都是一个 Five 处理函数:局部刷新的超媒体更新、服务端渲染的表单、CSV 导出、照片上传、实时事件。

02 — 写过 xBase 或 Harbour?

这门语言,你早就会了。
变的只是屏幕。

如果你写过 Clipper、FoxPro 或 Harbour,SpaceFive 的页面读起来就像你写过几百遍的对话框。下面的例子全是真的:右边的控件,就是左边这段代码在本次请求里当场画出来的。

菜单 — MENU … ENDMENU
MENU hMenu MENUBAR
   MENUITEM "Assets"      ACTION "/assets"
   MENUITEM "People"      ACTION "/people"
   MENUITEM "+ New asset" ACTION "/assets/new" GOLD
ENDMENU
cHtml := MENU_HTML( hMenu )
本次请求实时渲染

和 FiveWin 一模一样的写法。MENU_HTML() 负责生成标记,长什么样由皮肤说了算。

数据卡片 — DASHBOARD … STAT
hStat := DB_ONE( cAssetStats, {} )

DASHBOARD hDash
   STAT "Total assets"   VALUE hStat[ "n" ] GOLD
   STAT "Total value"    VALUE Money( hStat[ "total" ], "USD" ) GOLD
   STAT "Active"         VALUE hStat[ "active" ] OK
   STAT "In maintenance" VALUE hStat[ "maint" ] WARN
ENDDASHBOARD
cHtml := DASH_HTML( hDash )
本次请求实时渲染

一条查询,四行 STAT。GOLD、OK、WARN 是颜色,数字是刚从 PostgreSQL 读出来的。

Total assets
14
Total value
$66,745
Active
12
In maintenance
1
浏览表 — TABLE … COLUMN
aRows := DB_QUERY( cFirstEmployees, {} )

TABLE hList OF aRows LINK "/people/:id"
   COLUMN "name"       HEADER "Employee" THUMB "photo" SUB "emp_no"
   COLUMN "position"   HEADER "Position"
   COLUMN "status"     HEADER "Status" BADGE
   COLUMN "salary"     HEADER "Salary" MONEY CURRENCY "currency" RIGHT
ENDTABLE
cHtml := TABLE_HTML( hList )
本次请求实时渲染

Web 版的 TBrowse:每个字段一行 COLUMN,用 THUMB、BADGE、MONEY、RIGHT 说明怎么显示,加上 LINK 每一行都能点。

EmployeePositionStatusSalary
照片卡片 — GALLERY … CARD
GALLERY hCards OF aRows LINK "/people/:id"
   CARD PHOTO "photo"  INITIALS "name"
   CARD BADGE "status"
   CARD TAG   "position"
   CARD TITLE "name"
   CARD META  "department"
   CARD VALUE "salary" CURRENCY "currency"
ENDGALLERY
cHtml := GALLERY_HTML( hCards )
本次请求实时渲染

CARD 把字段放到卡片上的位置:照片、徽章、标签、标题、附注、数值。卡片本身只是皮肤目录里的一个 HTML 文件。

表单 — FORM … GET … COMBO
FORM hForm ACTION "/people" OF hReq
   GET   "emp_no" LABEL "Employee no." VALUE "E-2001"
   GET   "name"   LABEL "Name"
   COMBO "status" LABEL "Status" VALUE "active" ITEMS aStatuses
   GET   "salary" LABEL "Salary (annual)" VALUE 90000
   BUTTON "Save" GOLD
   LINK   "Cancel" ACTION "/"
ENDFORM
cHtml := FORM_HTML( hForm )
本次请求实时渲染

浏览器版的 @ SAY … GET。CSRF 令牌随表单一起提交,点下 Save,真的会新建一名员工。

Cancel

从 DOS 屏幕到浏览器

当年这么写现在这么写作用
@ 10, 5 SAY "Name" GET cNameGET "name" LABEL "Name" VALUE cName表单里一个带标签的输入框。
MENU oMenu … MENUITEM … ENDMENUMENU hMenu MENUBAR … MENUITEM … ENDMENU页眉按钮、侧栏筛选、选项卡,一套写法全搞定。
TBrowse / dbEdit()TABLE hList OF aRows … COLUMN … ENDTABLE可点击的列表,自带缩略图、徽章和金额列。
USE customers INDEX ON nameaRows := DB_QUERY( cSql, { cName } )参数绑定,直接查 PostgreSQL,没有 ORM 服务器。
SET COLOR TO W+/Bpublic/skins/<skin>/skin.css外观交给皮肤:设计师能直接改的 CSS 和几个小 HTML 模板。
ACTIVATE WINDOW oWndFV_SERVE( nPort, "public", {| hReq | APP_DISPATCH( hReq ) } )整个 Web 服务器,就是 main.prg 里的一行。
03 — 原理

运行时直接上手, 不靠胶水。

每一层都是 Five 运行时库的调用。除了 PostgreSQL,什么都不用装。

FV_SERVE

HTTP 全程自己来

收请求头和 Cookie,回状态码和 set-cookie。每个请求跑在自己的 VM 线程里。

WsServe · WsSend

实时

零依赖的 WebSocket 服务器,本页底部的动态流正在监听它。

PG_OPEN · PG_QUERY

直连 PostgreSQL

基于 pgx 的参数绑定查询。迁移在启动时自动执行,跑多少次结果都一样。

PWHASH · ENCRYPT · SECEQ

默认就安全

bcrypt 密码、AES-256-GCM 会话、恒定时间比较、CSRF 双重提交。

T()

内置多语言

目录式翻译,按请求决定语言。你正在看的这一页,就是由 locales/*.json 用六种语言提供的。

hx.js

超媒体 UI

54 行的 HTMX 风格小垫片:局部替换、CSRF 头,没有构建步骤,也没有前端框架。

04 — 代码

请求是哈希,响应也是哈希。
处理函数就是一个代码块。

这是正在运行的应用里真实的路由表和真实的处理函数。

routes/routes.prg
// asset manager — gallery, list, detail, CRUD, export
ROUTE( "GET",  "/assets",            {| hReq | ASSET_DASHBOARD( hReq ) } )
ROUTE( "GET",  "/assets/cards",      {| hReq | ASSET_CARDS( hReq ) } )
ROUTE( "POST", "/assets",            {| hReq | ASSET_CREATE( hReq ) } )
ROUTE( "GET",  "/assets/export.csv", {| hReq | ASSET_EXPORT( hReq ) } )

// main.prg — the whole server
FV_SERVE( hCfg[ "port" ], "public", {| hReq | APP_DISPATCH( hReq ) } )

// a handler: query → hash → JSON. The result lives in a variable; RETURN returns it.
FUNCTION ASSET_LIST( hReq )
   LOCAL aRows := Rows( hReq, 1000, 0 )
   LOCAL hRes  := RES_JSON( { "ok" => .T., "assets" => aRows, "count" => Len( aRows ) } )

RETURN hRes
 wss · /ws连接中
这是 RT_BROADCAST 推送的实时动态。在另一个标签页新建一条记录试试,马上就会出现在这里: 新建资产 · 新建员工