Tham chiếu

SDK & CLI

Các gói SDK, phân chia giữa SDK và runtime của POS, cấu hình TypeScript, defineExtension và lệnh pos-app.

Xem Markdown

Các gói

Gói Dùng ở Vai trò
@pancake-pos/ui-extensions extensions/, page/ Type cho pos và element pos-*, helper defineExtension, client cho trang iframe (/page)
@pancake-pos/ui page/ Theme và component theo style POS cho trang iframe
@pancake-pos/app-cli Máy dev, CI Lệnh pos-app: đăng nhập, chạy thử, kiểm tra, build, upload
@pancake-pos/eslint-plugin Editor, pos-app check Luật kiểm tra code cho app; editor báo lỗi ngay khi gõ
create-pos-app Một lần Tạo dự án mẫu, cài sẵn các gói trên và AGENTS.md

Phiên bản gói đi theo api_version: gói 2026.1.x dùng cho "api_version": "2026-01". CLI báo lỗi nếu hai giá trị không khớp.

SDK và runtime của POS

Code chạy trong sandbox gồm hai phần, do hai bên khác nhau cung cấp:

Text
Worker trong sandbox của app
┌─────────────────────────────────────────────────────┐
│ Bundle của app (shop build và upload)               │
│   module extension + Preact hoặc JS thuần           │
├─────────────────────────────────────────────────────┤
│ Runtime POS (POS cung cấp, không nằm trong bundle)  │
│   DOM tối giản · element pos-* · biến toàn cục pos  │
│   kênh postMessage · app token · gọi Open API       │
└─────────────────────────────────────────────────────┘
  • Runtime POS do POS nạp sẵn vào worker trước module của app. Vì runtime không nằm trong bundle, POS sửa lỗi bảo mật hoặc cải thiện runtime mà app không phải deploy lại, và module của app nhỏ.
  • @pancake-pos/ui-extensions khi dùng trong extensions/ chủ yếu cung cấp type và helper; gần như không thêm code vào bundle.
  • Trang iframe không có runtime nạp sẵn: app import connect() từ @pancake-pos/ui-extensions/page, đoạn client nhỏ này được bundle cùng trang.

Việc SDK và runtime lo thay app

Việc Chi tiết
Kênh giao tiếp postMessage giữa sandbox và POS, kiểm tra nguồn gửi, gửi lệnh và sự kiện hai chiều
Xác thực Lấy app token, gia hạn trước khi hết hạn, gặp 401 thì lấy token mới và thử lại 1 lần
Gọi Open API pos.api.*, gắn header Authorization, chặn method ngoài access.methods, đổi lỗi thành PosApiError
Worker UI DOM tối giản, định nghĩa element pos-*, gom thay đổi gửi về POS theo lô, chuyển sự kiện từ POS vào worker
Ngữ cảnh Cập nhật pos.data, phát sự kiện data-change
Trang iframe Đồng bộ URL với POS, setTitle, setPrimaryAction
Chế độ phát triển Nạp bundle từ localhost, tự nạp lại extension và trang sau mỗi lần build

Cài đặt

Dự án tạo bằng create-pos-app đã cài sẵn. Thêm vào dự án có sẵn:

Terminal
npm install @pancake-pos/ui-extensions preact
npm install -D @pancake-pos/app-cli typescript eslint @pancake-pos/eslint-plugin
npm install @pancake-pos/ui react react-dom   # chỉ khi có trang iframe

TypeScript

tsconfig.json ở gốc dự án dùng cho extensions/:

JSON
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "jsx": "react-jsx",
    "jsxImportSource": "preact",
    "strict": true,
    "types": ["@pancake-pos/ui-extensions"]
  },
  "include": ["extensions"]
}
  • types khai báo biến toàn cục pos và các element pos-* cho JSX (thuộc tính, sự kiện, event.detail).
  • Trang page/ có page/tsconfig.json riêng với "jsxImportSource": "react", do template sinh sẵn; không gộp chung vào tsconfig gốc.
  • extensions/ hỗ trợ Preact (khuyến nghị, nhẹ) và JS thuần dùng DOM API của runtime. Chưa hỗ trợ React trong extensions/: React 18 truyền thuộc tính dạng mảng/đối tượng thành chuỗi và không nghe được sự kiện của element pos-*; React 19 đang được đánh giá. Trang iframe dùng React bình thường.

defineExtension

Helper không bắt buộc, giúp type của tham số data và giá trị trả về khớp với target.

TypeScript
import { defineExtension } from '@pancake-pos/ui-extensions';

export default defineExtension('order.before_submit', async (data) => {
  // data: DetailsData<'order'>, phải trả BeforeSubmitResult
  if (data.draft.total_price > 20_000_000 && !data.draft.bill_phone_number) {
    return { ok: false, message: 'Đơn lớn cần số điện thoại.' };
  }
  return { ok: true };
});

defineExtension chỉ trả lại đúng hàm truyền vào, nên tương đương export default async function (data).

Resource lấy theo tên target, nên cùng helper này cho data.draft kiểu PurchaseDraft trong purchase.before_submit, hay data.records kiểu WarehouseTransferRef[] trong warehouse_transfer.index.selection-action.

Các type xuất ra từ gói: Resource, DetailsData, IndexData, SelectionData, AfterSaveInput, BeforeSubmitResult, HeadlessActionResult, PageData, cặp Ref/Draft của từng resource (OrderRef, OrderDraft, ProductRef, ProductDraft, PurchaseRef, PurchaseDraft, ExportRef, ExportDraft, WarehouseTransferRef, WarehouseTransferDraft), PosApiError, PosRuntimeError.

Trang iframe

TypeScript
import { connect } from '@pancake-pos/ui-extensions/page';

const pos = await connect({ timeout: 10_000 });
  • connect() chờ POS gửi ngữ cảnh và token; quá timeout (mặc định 10 giây) thì ném lỗi, thường do mở trang ngoài POS.
  • Trang chỉ chạy trong iframe của POS. Mở URL sandbox trên tab hay cửa sổ riêng, kể cả popup do trang tự mở, sẽ bị từ chối (403). Luồng đăng nhập kiểu popup phải quay về trang của bạn ngoài sandbox, rồi báo lại cho app qua server của bạn.
  • pos của trang có cùng API như trong worker, thêm pos.page.*. Xem Runtime API.

CLI (pos-app)

Lệnh Mô tả
pos-app login --token <mã> [--shop <shop_id>] Lưu phiên đăng nhập của CLI trên máy. Lệnh đầy đủ kèm mã lấy ở nút Kết nối CLI trên trang quản lý ứng dụng tuỳ biến (cần quyền Cấu hình); mã chỉ dùng cho shop đó và các lệnh của CLI (upload, xem version, lỗi, dữ liệu mẫu; không kích hoạt app hay đăng nhập POS được), hạn 30 ngày, hết hiệu lực khi bạn đăng xuất khỏi POS hoặc khi bị thu hồi ở nút Kết nối CLI
pos-app logout Thu hồi token CLI rồi xóa phiên đăng nhập trên máy
pos-app dev [--port 5174] [--page-url <url>] Build theo dõi thay đổi và phục vụ tại http://localhost:<port> cho chế độ phát triển của POS; POS tự nạp lại sau mỗi lần build. --page-url: trang tự host đang chạy trên máy (ví dụ http://localhost:3000)
pos-app check Kiểm tra manifest và code (xem bên dưới), không upload
pos-app build Build ra dist/
pos-app deploy [--shop <shop_id>] [--message "..."] Chạy check và build, upload thành version mới (chưa kích hoạt)
pos-app versions [--shop <shop_id>] Liệt kê version đã upload và version đang kích hoạt
pos-app mcp [--shop <shop_id>] Chạy MCP server cho công cụ AI, xem MCP

Mọi lệnh nhận --json để trả kết quả dạng máy đọc được (CI, script, MCP server).

Lệnh pos-app có trong dự án sau khi cài @pancake-pos/app-cli, nên npx pos-app … chạy được trong thư mục dự án. Ngoài dự án, gọi theo tên gói: npx @pancake-pos/app-cli <lệnh>. Gói pos-app trên npm không phải của Pancake.

Kích hoạt và rollback làm trên trang quản lý ứng dụng tuỳ biến trong POS, để admin luôn duyệt quyền trước khi app chạy.

pos-app check

Kiểm tra tĩnh, chạy tự động trước deploy. Đặc biệt hữu ích khi code do AI sinh:

  • Manifest: hợp lệ theo schema, báo đúng dòng; target có trong api_version; file module/entry tồn tại; khoá conditions hợp lệ theo resource.
  • Code: chạy bộ luật kiểm tra code với cấu hình có sẵn, không cần cấu hình gì trong dự án.
  • Kích thước module và bundle trong giới hạn.

Lỗi mức error chặn deploy; warning chỉ báo. Mỗi lỗi có file, dòng, mã luật và gợi ý sửa (--json cho CI và MCP). pos-app check bỏ qua comment eslint-disable, nên tắt luật trong code không vượt được bước kiểm tra này.

Luật kiểm tra code

Bộ luật nằm trong gói @pancake-pos/eslint-plugin. Luật về element sinh từ danh mục component của đúng api_version, nên luôn khớp với POS.

Nhóm Luật Mức
Môi trường worker Không dùng window, document.querySelector, localStorage, alert…; không import react, react-dom, file CSS hay thư viện UI trong extensions/ error
Element pos-* Element, thuộc tính, sự kiện có trong danh mục (gợi ý tên đúng khi gõ sai); giá trị enum hợp lệ; element con và slot hợp lệ; text nằm trong element chứa được text error
Dữ liệu và an toàn Method pos.api.* có trong access.methods; URL fetch và src của pos-image viết cố định thuộc network.allowed_origins (ảnh được dùng thêm public/); không viết cứng mã shop; không có chuỗi giống API key, token, secret error
Hành vi Module export default một hàm; danh sách render có key; không đặt value của ô nhập sau await; before_submit không gọi API nối tiếp nhiều lần warning
Trang iframe Không dùng window.top, window.parent; không dùng route bắt đầu bằng /_pos/ error

Kiểm tra tĩnh không bắt được trường hợp động, ví dụ tên element tạo từ biến. Ở chế độ phát triển, POS cảnh báo thêm trong console khi chạy.

Báo lỗi trong editor

Dự án tạo bằng create-pos-app có sẵn eslint.config.js:

JavaScript
import posApp from '@pancake-pos/eslint-plugin';

// recommended đọc pos-app.json để biết api_version, access và allowed_origins
export default [...posApp.configs.recommended];

Cài extension ESLint cho editor (VS Code, Cursor…) để thấy lỗi ngay khi gõ. npx eslint . cho cùng kết quả với phần code của pos-app check.

Kết quả build

Text
dist/
├─ pos-app.json              # manifest, đường dẫn module đã đổi sang file build
├─ ext/
│  ├─ order-block.js         # mỗi module một file ES module, đã gồm Preact
│  └─ push-erp.js
├─ public/                   # ảnh cho pos-image, chép nguyên từ dự án
└─ page/                     # trang iframe do Vite build
   ├─ index.html
   └─ assets/...
  • Module extension build bằng esbuild, đích ES2020, không tách chunk, không import từ CDN.
  • Trang iframe build bằng Vite với cấu hình có sẵn trong template.

MCP

pos-app mcp chạy một MCP server (stdio) để công cụ AI tra tài liệu, xem dữ liệu nền thật của shop, kiểm tra app, chạy thử, đọc lỗi, deploy và báo lỗi cho Pancake ngay trong cuộc trò chuyện.

Terminal
npx @pancake-pos/app-cli mcp --shop 123          # chạy trong thư mục dự án app, hoặc thêm --cwd <thư mục>

Cách thêm server vào Claude Code, Cursor, VS Code, Codex, Claude Desktop, danh sách tool và câu lệnh mẫu: Làm app với AI › MCP.