@vitrine-kit/core 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @vitrine-kit/core
2
2
 
3
- Vitrine's critical logic: the slot and adapter runtime, the order pipeline, and the Stripe webhook handler.
3
+ Vitrine's critical logic: the slot and adapter runtime, the order pipeline, and provider-neutral payment webhook dispatch.
4
4
 
5
5
  This is where "a bug = an incident for every client at once" (spec §4). That's why it's a **versioned package** rather than copy-in: a critical fix reaches everyone via a version bump.
6
+
7
+ ## Global registries and isolation
8
+
9
+ `slotRegistry`, `adapters`, and `payments` are **module-level singletons** — state is shared by
10
+ everything importing this package in one process. That is the right default for a Vitrine client
11
+ (1 client = 1 repository = 1 store; `lib/slots.ts` / `lib/payments.ts` register once at module
12
+ load, which is idempotent-safe because generated registration modules only run once per process).
13
+
14
+ Do NOT reach for the globals when you need isolation:
15
+
16
+ - **Tests** — create your own: `createSlotRegistry()`, `createAdapterRegistry()`,
17
+ `createPaymentRegistry()`; `<Slot registry={...}>` accepts one. Or `clear()` the global in a
18
+ `finally` block.
19
+ - **Multi-tenant / multi-store servers** — one process serving several stores must NOT share the
20
+ globals (registrations from one tenant would leak into another). Keep a registry instance per
21
+ tenant and pass it explicitly.
22
+
23
+ Registering the same mount twice (e.g. calling a `register<Feature>Slots()` again after HMR)
24
+ duplicates it — re-registration should go through `clear()` first.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { S as SlotRegistry, c as createSlotRegistry, g as getSlotMounts, r as registerSlot, s as slotRegistry } from './registry-CfORxf9H.js';
2
2
  import { Backend, SiteConfig, CatalogSource, CommerceBackend, Cart, Order, Money, CurrencyCode, OrderStatus } from '@vitrine-kit/contracts';
3
3
 
4
+ declare const CORE_VERSION: "0.3.0";
5
+
4
6
  interface AdapterFactory {
5
7
  backend: Backend;
6
8
  createCatalog(config: SiteConfig): CatalogSource;
@@ -27,7 +29,7 @@ interface OrderPipelineContext {
27
29
  type OrderStage<T = OrderPipelineContext> = (ctx: T) => T | Promise<T>;
28
30
  /**
29
31
  * Runs the context through the stages sequentially. Any stage error aborts the
30
- * pipeline (rollback/idempotency are the responsibility of specific stages in M8).
32
+ * pipeline (rollback/idempotency are the responsibility of specific stages).
31
33
  */
32
34
  declare function runPipeline<T>(ctx: T, stages: ReadonlyArray<OrderStage<T>>): Promise<T>;
33
35
 
@@ -141,6 +143,19 @@ interface BuildOrderOptions {
141
143
  /** Snapshots the cart into an order (after payment). Totals come from the cart — not recomputed. */
142
144
  declare function buildOrderFromCart(cart: Cart, opts: BuildOrderOptions): Order;
143
145
 
144
- declare const CORE_VERSION: "0.1.0";
146
+ interface RateLimitOptions {
147
+ /** Max requests allowed within the window. */
148
+ limit: number;
149
+ /** Window size in milliseconds. */
150
+ windowMs: number;
151
+ }
152
+ interface RateLimitResult {
153
+ allowed: boolean;
154
+ /** Milliseconds until the caller may retry — present only when !allowed. */
155
+ retryAfterMs?: number;
156
+ }
157
+ declare function checkRateLimit(key: string, { limit, windowMs }: RateLimitOptions): RateLimitResult;
158
+ /** Best-effort client IP from standard proxy headers (Web Fetch API `Headers`). */
159
+ declare function clientIpFromHeaders(headers: Headers): string;
145
160
 
146
- export { type AdapterFactory, type AdapterRegistry, type BuildOrderOptions, CORE_VERSION, type CreateCheckoutArgs, type NewLineInput, type NormalizedPaymentEvent, type OrderCreationGuard, type OrderPipelineContext, type OrderStage, type PaymentProvider, type PaymentProviderName, type PaymentRegistry, type PaymentWebhookHandlers, type PaymentWebhookRequest, type PaymentWebhookResult, adapters, addCartLine, buildOrderFromCart, cartItemCount, computeLineTotal, createAdapterRegistry, createPaymentRegistry, emptyCart, handlePaymentWebhook, payments, recalcCart, removeCartLine, runPipeline, setCartLineQty, shouldCreateOrder };
161
+ export { type AdapterFactory, type AdapterRegistry, type BuildOrderOptions, CORE_VERSION, type CreateCheckoutArgs, type NewLineInput, type NormalizedPaymentEvent, type OrderCreationGuard, type OrderPipelineContext, type OrderStage, type PaymentProvider, type PaymentProviderName, type PaymentRegistry, type PaymentWebhookHandlers, type PaymentWebhookRequest, type PaymentWebhookResult, type RateLimitOptions, type RateLimitResult, adapters, addCartLine, buildOrderFromCart, cartItemCount, checkRateLimit, clientIpFromHeaders, computeLineTotal, createAdapterRegistry, createPaymentRegistry, emptyCart, handlePaymentWebhook, payments, recalcCart, removeCartLine, runPipeline, setCartLineQty, shouldCreateOrder };
package/dist/index.js CHANGED
@@ -5,6 +5,9 @@ import {
5
5
  slotRegistry
6
6
  } from "./chunk-BQVNYHVU.js";
7
7
 
8
+ // src/version.generated.ts
9
+ var CORE_VERSION = "0.3.0";
10
+
8
11
  // src/adapter/resolver.ts
9
12
  function createAdapterRegistry() {
10
13
  const factories = /* @__PURE__ */ new Map();
@@ -184,14 +187,34 @@ function buildOrderFromCart(cart, opts) {
184
187
  };
185
188
  }
186
189
 
187
- // src/index.ts
188
- var CORE_VERSION = "0.1.0";
190
+ // src/security/rate-limit.ts
191
+ var buckets = /* @__PURE__ */ new Map();
192
+ function checkRateLimit(key, { limit, windowMs }) {
193
+ const now = Date.now();
194
+ const bucket = buckets.get(key);
195
+ if (!bucket || bucket.resetAt <= now) {
196
+ buckets.set(key, { count: 1, resetAt: now + windowMs });
197
+ return { allowed: true };
198
+ }
199
+ if (bucket.count >= limit) {
200
+ return { allowed: false, retryAfterMs: bucket.resetAt - now };
201
+ }
202
+ bucket.count += 1;
203
+ return { allowed: true };
204
+ }
205
+ function clientIpFromHeaders(headers) {
206
+ const forwarded = headers.get("x-forwarded-for");
207
+ const first = forwarded?.split(",")[0]?.trim();
208
+ return first || headers.get("x-real-ip") || "unknown";
209
+ }
189
210
  export {
190
211
  CORE_VERSION,
191
212
  adapters,
192
213
  addCartLine,
193
214
  buildOrderFromCart,
194
215
  cartItemCount,
216
+ checkRateLimit,
217
+ clientIpFromHeaders,
195
218
  computeLineTotal,
196
219
  createAdapterRegistry,
197
220
  createPaymentRegistry,
package/dist/react.js CHANGED
@@ -12,10 +12,17 @@ function Slot(props) {
12
12
  const reg = registry ?? slotRegistry;
13
13
  const mounts = reg.get(name);
14
14
  if (mounts.length === 0) return fallback;
15
+ const seen = /* @__PURE__ */ new Map();
15
16
  return createElement(
16
17
  Fragment,
17
18
  null,
18
- ...mounts.map((m, i) => createElement(m.component, { key: i, ...rest }))
19
+ ...mounts.map((m) => {
20
+ const c = m.component;
21
+ const base = c.displayName ?? (c.name || "mount");
22
+ const n = seen.get(base) ?? 0;
23
+ seen.set(base, n + 1);
24
+ return createElement(m.component, { key: n === 0 ? base : `${base}:${n}`, ...rest });
25
+ })
19
26
  );
20
27
  }
21
28
  export {
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@vitrine-kit/core",
3
- "version": "0.2.1",
4
- "description": "Vitrine core — slot/adapter runtime, order pipeline, Stripe webhook. Critical logic (a bug = an incident for everyone).",
3
+ "version": "0.3.0",
4
+ "description": "Vitrine core — slot/adapter runtime, order pipeline, provider-neutral payment webhook dispatch. Critical logic (a bug = an incident for everyone).",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
+ "sideEffects": false,
8
+ "engines": {
9
+ "node": ">=20"
10
+ },
7
11
  "types": "./dist/index.d.ts",
8
12
  "main": "./dist/index.js",
9
13
  "module": "./dist/index.js",
@@ -29,7 +33,7 @@
29
33
  "directory": "packages/core"
30
34
  },
31
35
  "dependencies": {
32
- "@vitrine-kit/contracts": "1.2.0"
36
+ "@vitrine-kit/contracts": "1.2.1"
33
37
  },
34
38
  "peerDependencies": {
35
39
  "react": ">=18.0.0"
@@ -44,8 +48,8 @@
44
48
  "react": "^18.3.1"
45
49
  },
46
50
  "scripts": {
47
- "build": "tsup src/index.ts src/react.ts --format esm --dts --clean",
48
- "typecheck": "tsc --noEmit",
51
+ "build": "node scripts/generate-version.mjs && tsup src/index.ts src/react.ts --format esm --dts --clean",
52
+ "typecheck": "node scripts/generate-version.mjs && tsc --noEmit",
49
53
  "test": "vitest run --passWithNoTests"
50
54
  }
51
55
  }