diff --git a/README.md b/README.md index 532fd58d..fb9bbd23 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ -# Urumi +# Urumi — an open-source commerce layer for EmDash -A commerce layer for [EmDash](https://github.com/emdash-cms/emdash) — the WooCommerce-equivalent for Cloudflare's TypeScript CMS. +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) +[![Version](https://img.shields.io/badge/version-0.0.1-orange.svg)](https://github.com/UrumiAI/otta.sh/releases/tag/v0.0.1) + +Open source (MIT), version 0.0.1. The WooCommerce-equivalent for +[EmDash](https://github.com/emdash-cms/emdash), Cloudflare's TypeScript CMS. ![The Urumi storefront: a product listing with three sample products, each showing a title, description, and price](./docs/storefront.png) @@ -9,7 +13,7 @@ service — this is what the [quick start](#quick-start-local-2-minutes) below g ## What this is -Urumi turns an EmDash site into a store. It ships as two parts: +Urumi turns an EmDash site into a store. It ships as three parts: 1. **Urumi plugin** — a sandbox-clean EmDash plugin: storefront routes, an on-screen "Product data" panel (Block Kit field widget), content-sync hooks, cart/checkout @@ -19,6 +23,12 @@ Urumi turns an EmDash site into a store. It ships as two parts: 2. **Urumi commerce service** — a standalone Node/Hono + Postgres service that owns all money and stock truth: catalog, inventory, cart, checkout, orders, customers, payments, tax, shipping, discounts, entitlements, reporting, and webhooks. +3. **The reference site** (`sites/staging`) — a default EmDash site with the plugin already + registered, so there's something to actually run. It's the storefront in the screenshot + above and what the [quick start](#quick-start-local-2-minutes) boots: product listing + pages, cart, and the admin console. Treat it as the worked example to copy from when + wiring Urumi into your own site — it covers **catalog + cart only** today (see + [Status](#status)). ## Quick start (local, ~2 minutes) @@ -67,20 +77,22 @@ To deploy this for free on Cloudflare Workers, follow ## Why two parts -**The biggest blocker right now is a missing primitive: EmDash's plugin sandbox has no -atomic write / compare-and-set / transaction.** Plugins run with no direct DB access — all -data crosses a capability-scoped RPC bridge as JSON copies — and `ctx.storage` is an -unconditional upsert whose declared unique indexes are silently downgraded. So a safe -inventory decrement (read-then-write across two bridge calls) always races under -concurrency, and nothing in the plugin surface closes it. Until that primitive exists, -correct commerce needs a transactional database off to the side. The commerce service -holds it and performs the one atomic operation that matters: - -```sql -UPDATE inventory SET on_hand = on_hand - :q - WHERE sku = :s AND on_hand >= :q -RETURNING on_hand; -- 0 rows = out of stock. No oversell, no lock. -``` +**EmDash's plugin sandbox has no atomic write, compare-and-set, or transaction.** Plugins +get no direct database access — everything crosses a capability-scoped RPC bridge as JSON +copies — and `ctx.storage` is an unconditional upsert whose declared unique indexes are +silently downgraded. Any read-then-write spans two bridge calls and can interleave, so the +sandbox can't express a guarded update, a uniqueness constraint, or a multi-document +commit. Those are the ordinary building blocks of an order pipeline, so for now the +transactional database sits off to the side, in the commerce service. + +The gap is closing. [emdash-cms/emdash#2169](https://github.com/emdash-cms/emdash/pull/2169) +(ours, currently a draft) adds `ctx.storage..updateIf(id, { where, set?, +delta? })` — a guarded `UPDATE … RETURNING` run inside the sandbox. Two sibling primitives, +not yet proposed upstream, cover the rest: an atomic `insert` that classifies unique +violations, and `ctx.storage.batch([...])` for all-or-nothing multi-collection writes. +With all three, a `@urumi/store-emdash` adapter already passes the domain's full +`InventoryStore` contract in-process. Once orders, payments, webhooks, and reporting +follow, the split becomes a deployment choice rather than a correctness requirement. ## Architecture (summary) @@ -128,12 +140,15 @@ pnpm test # vitest (better-sqlite3 by default) pnpm format # oxfmt, tabs ``` -The **no-oversell concurrency test is Postgres-required** — better-sqlite3 serializes -writes in one process, so it verifies the SQL is correct, not that it's race-safe. See +The **concurrency tests are Postgres-required** — better-sqlite3 serializes writes in one +process, so it verifies the SQL is correct, not that it's race-safe under contention. See `DEVELOPMENT.md` for the TDD / contract-first workflow and commerce invariants. ## Status +**v0.0.1** — first open-source release. The `@urumi/*` packages are all at `0.0.1` and are +not published to npm yet; consume them from the workspace. + The commerce **service** is feature-complete (Phases 0–7 merged): catalog, inventory, cart, checkout, orders, customers with magic-link auth, Stripe + x402 payments, tax, shipping, discounts, entitlements, reporting, and settings. diff --git a/packages/domain/package.json b/packages/domain/package.json index 7434ef5b..890250c0 100644 --- a/packages/domain/package.json +++ b/packages/domain/package.json @@ -1,6 +1,6 @@ { "name": "@urumi/domain", - "version": "0.1.0", + "version": "0.0.1", "description": "Urumi commerce domain — pure ports, use-cases, and branded types. No IO.", "homepage": "https://github.com/UrumiAI/otta.sh#readme", "bugs": { diff --git a/packages/payments-stripe/package.json b/packages/payments-stripe/package.json index a25c6707..7a952854 100644 --- a/packages/payments-stripe/package.json +++ b/packages/payments-stripe/package.json @@ -1,6 +1,6 @@ { "name": "@urumi/payments-stripe", - "version": "0.1.0", + "version": "0.0.1", "description": "Stripe PaymentGateway adapter for Urumi — async-webhook confirmation with raw-body HMAC verification. Secrets are service-env only.", "homepage": "https://github.com/UrumiAI/otta.sh#readme", "bugs": { diff --git a/packages/payments-x402/package.json b/packages/payments-x402/package.json index 0c9c0b7c..533a8fa4 100644 --- a/packages/payments-x402/package.json +++ b/packages/payments-x402/package.json @@ -1,6 +1,6 @@ { "name": "@urumi/payments-x402", - "version": "0.1.0", + "version": "0.0.1", "description": "x402 PaymentGateway adapter for Urumi — synchronous page-gate proof, facilitator-verified server-side (never trusting the plugin).", "homepage": "https://github.com/UrumiAI/otta.sh#readme", "bugs": { diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 9f778c1a..fefc8928 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,6 +1,6 @@ { "name": "@urumi/plugin", - "version": "0.1.0", + "version": "0.0.1", "description": "Urumi EmDash plugin — sandbox-clean (workerd, Block Kit, ctx.http + allowedHosts only). No React, no DB/storage capability.", "homepage": "https://github.com/UrumiAI/otta.sh#readme", "bugs": { diff --git a/packages/service/package.json b/packages/service/package.json index 1615c7a3..2c1303dc 100644 --- a/packages/service/package.json +++ b/packages/service/package.json @@ -1,6 +1,6 @@ { "name": "@urumi/service", - "version": "0.1.0", + "version": "0.0.1", "description": "Urumi commerce service — thin Hono REST API mirroring the domain ports 1:1.", "homepage": "https://github.com/UrumiAI/otta.sh#readme", "bugs": { diff --git a/packages/store-postgres/package.json b/packages/store-postgres/package.json index 00b716fa..329b21fd 100644 --- a/packages/store-postgres/package.json +++ b/packages/store-postgres/package.json @@ -1,6 +1,6 @@ { "name": "@urumi/store-postgres", - "version": "0.1.0", + "version": "0.0.1", "description": "Kysely-backed store adapters for Urumi — dialect-parameterized over better-sqlite3 (local) and pg (CI/prod).", "homepage": "https://github.com/UrumiAI/otta.sh#readme", "bugs": {