购物车:签名访客 Cookie、登录时合并、服务端定价和可扩展的金额计算流水线
代码10 个文件上下文约 611 个 token扫描通过
安装
$
genpm add @core/cart你将获得
- 源代码位于 src/lib/cart/,共 10 个文件。 (24.9 kB)
- AI 规则位于 src/lib/cart/AGENTS.md,另附 IDE 规则文件。
- 添加到 .env.example 的环境变量:CART_COOKIE_SECRET。
- 自动为你解析 @core/auth, @core/catalog, @core/contracts, @core/db, @core/money。
README
此包没有 README。
约 611 个 token→ src/lib/cart/AGENTS.md→ .cursor/rules/genpm-core-cart.mdc
这正是你的 AI 在 src/lib/cart 中工作时读取的内容。不会向其上下文添加其他任何内容。
@core/cart — rules for AI agents
Purpose
Carts for guests (id in an HMAC-signed HttpOnly cookie) and users (merged on login), lines per variant with stock
and quantity checks, and computeTotals: lines are always priced from @core/catalog in the database, unavailable
items are dropped with an issues entry, then registered steps run (discounts 100, shipping 200, taxes 300).
No payments or orders. Tables: carts, cart_lines.
Map
index.ts— public API:getCart,getOrCreateCart,addLine,setLine,setDiscountCodes,setCartMeta,computeTotals,registerTotalsStep,applyOrderDiscount,registerPendingCodeUses,pendingCodeUses,pruneCarts.cart.ts— operations.totals.ts— pipeline and types.cookie.ts— signed cookie.adapters/hono.ts—cartRoutes({ currentUserId }).adapters/next.ts—cartRoute,cartLinesRoute,cartLineRoute,cartCodesRoute.
Integration
- Env:
CART_COOKIE_SECRET(≥ 32 chars). Migrations as insrc/lib/db/AGENTS.md. - Mount the JSON API at
/api/cartwithcurrentUserIdfrom @core/auth (orasync () => nullfor guest-only shops). Calls:GET /api/cart,POST /api/cart/lines {variantId, quantity},PUT /api/cart/lines/:variantId {quantity}(0 removes),PUT /api/cart/codes {codes}. - Server pages:
const cart = await getCart(cookieHeader, userId); const totals = cart && await computeTotals(cart). - Modules that change totals register a step once at startup:
registerTotalsStep({ name, order, run: (t) => … }); order discounts must go throughapplyOrderDiscountso they are allocated to lines. Modules that open payments before the order exists (@core/checkout) registerregisterPendingCodeUsesso usage-limited codes (@core/discounts) count in-flight payments as reserved. - Schedule
pruneCarts()daily (@core/jobs; with kit-store, itsstore.maintenancejob does it). - Verify: add a product, change its price in the admin, the cart shows the new price.
Conventions
- Totals shown and charged come from
computeTotalsonly; never from values sent by the browser. - Show every
issuesentry to the customer before checkout. - Money values are
{ amount, currency }in minor units (@core/money).
Don't
- Don't reserve stock in the cart (that happens when the order is created).
- Don't trust prices, discounts or totals in request bodies, and don't read the cart cookie without verifying it.
# @core/cart — rules for AI agents
## Purpose
Carts for guests (id in an HMAC-signed HttpOnly cookie) and users (merged on login), lines per variant with stock
and quantity checks, and `computeTotals`: lines are always priced from @core/catalog in the database, unavailable
items are dropped with an `issues` entry, then registered steps run (discounts 100, shipping 200, taxes 300).
No payments or orders. Tables: `carts`, `cart_lines`.
## Map
- `index.ts` — public API: `getCart`, `getOrCreateCart`, `addLine`, `setLine`, `setDiscountCodes`, `setCartMeta`, `computeTotals`, `registerTotalsStep`, `applyOrderDiscount`, `registerPendingCodeUses`, `pendingCodeUses`, `pruneCarts`.
- `cart.ts` — operations. `totals.ts` — pipeline and types. `cookie.ts` — signed cookie.
- `adapters/hono.ts` — `cartRoutes({ currentUserId })`. `adapters/next.ts` — `cartRoute`, `cartLinesRoute`, `cartLineRoute`, `cartCodesRoute`.
## Integration
1. Env: `CART_COOKIE_SECRET` (≥ 32 chars). Migrations as in `src/lib/db/AGENTS.md`.
2. Mount the JSON API at `/api/cart` with `currentUserId` from @core/auth (or `async () => null` for guest-only shops).
Calls: `GET /api/cart`, `POST /api/cart/lines {variantId, quantity}`, `PUT /api/cart/lines/:variantId {quantity}` (0 removes), `PUT /api/cart/codes {codes}`.
3. Server pages: `const cart = await getCart(cookieHeader, userId); const totals = cart && await computeTotals(cart)`.
4. Modules that change totals register a step once at startup: `registerTotalsStep({ name, order, run: (t) => … })`;
order discounts must go through `applyOrderDiscount` so they are allocated to lines.
Modules that open payments before the order exists (@core/checkout) register `registerPendingCodeUses` so
usage-limited codes (@core/discounts) count in-flight payments as reserved.
5. Schedule `pruneCarts()` daily (@core/jobs; with kit-store, its `store.maintenance` job does it).
6. Verify: add a product, change its price in the admin, the cart shows the new price.
## Conventions
- Totals shown and charged come from `computeTotals` only; never from values sent by the browser.
- Show every `issues` entry to the customer before checkout.
- Money values are `{ amount, currency }` in minor units (@core/money).
## Don't
- Don't reserve stock in the cart (that happens when the order is created).
- Don't trust prices, discounts or totals in request bodies, and don't read the cart cookie without verifying it.
应用 .genpmignore 后将被注入的确切目录树。固定于
// API JSON del carrito: GET (totales), POST /lines (añadir), PUT /lines/:variantId (cantidad), PUT /codes.
import { z } from 'zod';
import { addLine, CartError, computeTotals, getCart, getOrCreateCart, setDiscountCodes, setLine } from '../index.ts';
export type CurrentUserId = (req: Request) => Promise<string | null>;
const STATUS = { not_found: 404, invalid: 400, unavailable: 409, insufficient_stock: 409 } as const;
async function respond(req: Request, userId: string | null, run?: (cartId: string) => Promise<void>): Promise<Response> {
const cookie = req.headers.get('cookie');
if (!run) {
const cart = await getCart(cookie, userId);
return Response.json(cart ? await computeTotals(cart) : { lines: [], issues: [] }, { headers: { 'cache-control': 'no-store' } });
}
const { cart, setCookie } = await getOrCreateCart(cookie, userId);
try {
await run(cart.id);
} catch (e) {
if (e instanceof CartError) return Response.json({ error: e.code, message: e.message }, { status: STATUS[e.code] });
throw e;
}
const headers: Record<string, string> = { 'cache-control': 'no-store' };
if (setCookie) headers['set-cookie'] = setCookie;
return Response.json(await computeTotals(cart), { headers });
}
const Add = z.object({ variantId: z.string().min(1).max(64), quantity: z.number().int().min(1).max(99).default(1) });
const Qty = z.object({ quantity: z.number().int().min(0).max(99) });
const Codes = z.object({ codes: z.array(z.string().max(40)).max(5) });
async function body<T>(req: Request, schema: z.ZodType<T>): Promise<T | Response> {
const r = schema.safeParse(await req.json().catch(() => null));
return r.success ? r.data : Response.json({ error: 'invalid' }, { status: 400 });
}
export const cartHandlers = (currentUserId: CurrentUserId) => ({
get: async (req: Request) => respond(req, await currentUserId(req)),
add: async (req: Request) => {
const b = await body(req, Add);
return b instanceof Response ? b : respond(req, await currentUserId(req), (id) => addLine(id, b.variantId, b.quantity));
},
setQuantity: async (req: Request, variantId: string) => {
const b = await body(req, Qty);
return b instanceof Response ? b : respond(req, await currentUserId(req), (id) => setLine(id, variantId, b.quantity));
},
setCodes: async (req: Request) => {
const b = await body(req, Codes);
return b instanceof Response ? b : respond(req, await currentUserId(req), (id) => setDiscountCodes(id, b.codes));
},
});
此包未声明 MCP 服务器。
| 版本 | 提交 | 发布时间 | 扫描 |
|---|---|---|---|
| 1.1.0 | 787ea88 | 3小时前 | 扫描通过 |
- npm
- zod ^4.0.0
- 建议
- GenPM 会给出 npm 命令建议,只有你同意时才会运行。
- 扫描
- 扫描通过 · 0 个问题
- 提交
- v1.1.0 → 787ea88d1eb25e11257f051f971c0bcce52803f6 · 获取后已校验
- 脚本
- 无。GenPM 从不运行包中的代码。
- 许可证
- MIT
- 质量
- 100/100
- 可识别的许可证已满足
- AGENTS.md 说明了用途已满足
- AGENTS.md 包含集成步骤已满足
- AGENTS.md 列出约定或禁止事项已满足
- 包含测试已满足
- 通过安全扫描已满足
- 最近 6 个月内发布已满足
- 已验证的发布者已满足
- 摘要和关键词已满足
- 举报
- 发现问题了吗?