言語はひとつ。
バイナリもひとつ。
それで、Web のすべて。
SpaceFive は、純粋な Five だけで書かれた、必要なものがすべて揃った Web フレームワークです。HTTP も WebSocket も PostgreSQL も、bcrypt、AES、多言語対応まで、すべてランタイムが直接こなします。Node も FFI も ORM サーバーも要りません。静的バイナリひとつが Postgres とじかにやり取りし、その上で本物のアプリが 3 つ、いままさに動いています。
アプリは 3 つ、 骨組みはひとつ、
手ざわりは同じ。
クリックするものはすべて Five のハンドラが返しています。画面の一部だけ差し替えるハイパーメディア更新、サーバーで描いたフォーム、CSV 書き出し、写真アップロード、リアルタイムイベントまで。
資産管理
ギャラリー・一覧・詳細の 3 画面、検索と状態・分類フィルター、複数通貨の金額、ワンクリックの CSV 書き出し。
人事管理
役職・部署・給与・写真つきの社員名簿。資産管理とまったく同じシェル、チップ、カードでできています。
販売管理 ERP
得意先・商品・受注を、与信管理、在庫、出荷、請求、入金、売掛金の年齢表までひとつの流れで。ダッシュボード、印刷用伝票、レポート付き。
この言語、もう知っていますよね。
変わったのは画面だけ。
Clipper や FoxPro、Harbour を書いたことがあれば、SpaceFive のページは何百回も作ったダイアログのように読めるはずです。下の例はすべて本物。右のウィジェットは、左のコードがこのリクエストでそのまま描いたものです。
MENU hMenu MENUBAR MENUITEM "Assets" ACTION "/assets" MENUITEM "People" ACTION "/people" MENUITEM "+ New asset" ACTION "/assets/new" GOLD ENDMENU cHtml := MENU_HTML( hMenu )
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 )
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 )
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 )
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 )
DOS の画面からブラウザへ
| あの頃のコード | いまのコード | 役割 |
|---|---|---|
| @ 10, 5 SAY "Name" GET cName | GET "name" LABEL "Name" VALUE cName | ラベル付きの入力欄がひとつ。 |
| MENU oMenu … MENUITEM … ENDMENU | MENU hMenu MENUBAR … MENUITEM … ENDMENU | ヘッダーのボタンも、サイドバーのチップも、タブも同じ文法で。 |
| TBrowse / dbEdit() | TABLE hList OF aRows … COLUMN … ENDTABLE | サムネイル・バッジ・金額列つきの、クリックできる一覧。 |
| USE customers INDEX ON name | aRows := DB_QUERY( cSql, { cName } ) | PostgreSQL にパラメータをバインドして直接問い合わせ。ORM サーバーは不要。 |
| SET COLOR TO W+/B | public/skins/<skin>/skin.css | 見た目はスキンの担当。デザイナーが手を入れられる CSS と小さな HTML テンプレートです。 |
| ACTIVATE WINDOW oWnd | FV_SERVE( nPort, "public", {| hReq | APP_DISPATCH( hReq ) } ) | Web サーバーの全部が main.prg の 1 行。 |
ランタイムが直接、 つなぎなし。
すべての層が Five ランタイムライブラリの呼び出しです。PostgreSQL のほかに入れるものはありません。
FV_SERVEHTTP を端から端まで
ヘッダーと Cookie を受け取り、ステータスと set-cookie を返す。リクエストごとに専用の VM スレッドで動きます。
WsServe · WsSendリアルタイム
依存ゼロの WebSocket サーバー。このページ下部のフィードが、いまそれを聞いています。
PG_OPEN · PG_QUERYPostgreSQL に直結
pgx の上でパラメータをバインドしたクエリ。マイグレーションは起動時に自動で、何度走らせても同じ結果。
PWHASH · ENCRYPT · SECEQ初期設定で安全
bcrypt のパスワード、AES-256-GCM のセッション、定数時間比較、CSRF ダブルサブミット。
T()多言語を標準装備
カタログ方式の翻訳を、リクエストごとのロケールで。いま見ているこのページも locales/*.json から 6 言語で配信しています。
hx.jsハイパーミディア UI
54 行の HTMX 風シムがひとつ。部分差し替えと CSRF ヘッダーだけ。ビルド工程もフレームワークもありません。
リクエストもハッシュ、レスポンスもハッシュ。
ハンドラはコードブロックひとつ。
いま動いているアプリの、本物のルート表と本物のハンドラです。
// 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






