ショッピングカート:署名付きゲスト Cookie、ログイン時の統合、サーバー側価格、拡張可能な合計計算
コード10 ファイルコンテキスト約 611 トークンスキャン合格
インストール
$
genpm add @core/cart含まれるもの
- src/lib/cart/ にソースコード(10 ファイル)。 (24.9 KB)
- src/lib/cart/AGENTS.md に AI ルール、加えて IDE 用のルールファイル。
- .env.example に追加される環境変数: CART_COOKIE_SECRET。
- @core/auth, @core/catalog, @core/contracts, @core/db, @core/money を自動で解決します。
README
このパッケージには README がありません。
約 611 トークン→ 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 適用後に組み込まれる正確なツリーです。固定先:
// Cookie del carrito: `<id>.<hmac>` firmada con CART_COOKIE_SECRET, HttpOnly, SameSite=Lax.
const enc = new TextEncoder();
export const CART_COOKIE = 'cart';
export const CART_TTL_DAYS = 30;
function secret(): string {
const s = process.env.CART_COOKIE_SECRET;
if (!s || s.length < 32) throw new Error('CART_COOKIE_SECRET must be set (>= 32 chars)');
return s;
}
async function sign(value: string): Promise<string> {
const key = await crypto.subtle.importKey('raw', enc.encode(secret()), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
const sig = new Uint8Array(await crypto.subtle.sign('HMAC', key, enc.encode(value)));
return btoa(String.fromCharCode(...sig)).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');
}
export async function cartCookie(cartId: string, secure = process.env.NODE_ENV === 'production'): Promise<string> {
return `${CART_COOKIE}=${cartId}.${await sign(cartId)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${CART_TTL_DAYS * 86_400}${secure ? '; Secure' : ''}`;
}
export const clearCartCookie = () => `${CART_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`;
/** Id del carrito de la cabecera Cookie si la firma es válida. */
export async function cartIdFromCookie(cookieHeader: string | null | undefined): Promise<string | null> {
const raw = (cookieHeader ?? '').split(/;\s*/).find((c) => c.startsWith(`${CART_COOKIE}=`))?.slice(CART_COOKIE.length + 1);
if (!raw) return null;
const dot = raw.lastIndexOf('.');
if (dot < 1) return null;
const id = raw.slice(0, dot);
const sig = raw.slice(dot + 1);
const expected = await sign(id);
let diff = sig.length ^ expected.length;
for (let i = 0; i < expected.length; i++) diff |= (sig.charCodeAt(i) || 0) ^ expected.charCodeAt(i);
return diff === 0 ? id : null;
}
このパッケージは MCP サーバーを宣言していません。
| バージョン | コミット | 公開日 | スキャン |
|---|---|---|---|
| 1.1.0 | 787ea88 | 4 時間前 | スキャン合格 |
- npm
- zod ^4.0.0
- 提案
- GenPM は npm コマンドを提案し、あなたが承認した場合にのみ実行します。
- スキャン
- スキャン合格 · 指摘 0 件
- コミット
- v1.1.0 → 787ea88d1eb25e11257f051f971c0bcce52803f6 · 取得後に検証済み
- スクリプト
- なし。GenPM はパッケージのコードを実行しません。
- ライセンス
- MIT
- 品質
- 100/100
- 認識されたライセンス達成
- AGENTS.md に目的の説明がある達成
- AGENTS.md に統合手順がある達成
- AGENTS.md に規約や禁止事項がある達成
- テストを含む達成
- セキュリティスキャンに合格達成
- 過去 6 か月以内に公開達成
- 認証済みの公開者達成
- 概要とキーワード達成
- 報告
- 問題を見つけましたか?