@porulle/core 0.21.0 → 0.23.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.
@@ -1 +1 @@
1
- {"version":3,"file":"actor.d.ts","sourceRoot":"","sources":["../../src/auth/actor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,eAAO,MAAM,kBAAkB,OAAO,CAAC;AACvC,eAAO,MAAM,mBAAmB,qBAAwC,CAAC;AAEzE,eAAO,MAAM,4BAA4B,0JAS/B,CAAC;AAEX,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,EAAE,CAEvE;AAsBD,8EAA8E;AAC9E,wBAAsB,YAAY,CAChC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,OAAsD,GAC9D,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CA2EvB"}
1
+ {"version":3,"file":"actor.d.ts","sourceRoot":"","sources":["../../src/auth/actor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAmB/C,eAAO,MAAM,kBAAkB,OAAO,CAAC;AACvC,eAAO,MAAM,mBAAmB,qBAAwC,CAAC;AAEzE,eAAO,MAAM,4BAA4B,0JAS/B,CAAC;AAEX,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,EAAE,CAEvE;AAkED,8EAA8E;AAC9E,wBAAsB,YAAY,CAChC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,OAAsD,GAC9D,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAiDvB"}
@@ -31,6 +31,44 @@ function toIsoString(value) {
31
31
  const date = value instanceof Date ? value : typeof value === "string" ? new Date(value) : null;
32
32
  return date !== null && Number.isFinite(date.getTime()) ? date.toISOString() : null;
33
33
  }
34
+ /**
35
+ * The caller's role in one organization, by one indexed read of `member`.
36
+ *
37
+ * This used to go through the organization plugin's endpoints, and that cost six
38
+ * statements instead of one. `getFullOrganization` loads the organization row,
39
+ * its invitations and its ENTIRE member list, then scans in JavaScript for a
40
+ * single membership; `getActiveMemberRole` then looks for the same membership
41
+ * again. Both take `headers` and re-resolve the session from them, so each also
42
+ * re-reads `session` and `user`, and the plugin writes `active_organization_id`
43
+ * back to the session row — a write on the hot path of every GET.
44
+ *
45
+ * For a shopper every one of those is a guaranteed miss: a shopper is not a
46
+ * member of the platform organization and never will be. The member-by-
47
+ * organization scan also grows with the member list, so the platform's busiest
48
+ * request got slower as the platform got bigger.
49
+ *
50
+ * The adapter read below is the same query the plugin ended with, issued once
51
+ * and without re-resolving anything. `findOne` returning null IS the answer for
52
+ * a shopper — one miss, done.
53
+ */
54
+ async function findMembershipRole(auth, userId, organizationId) {
55
+ try {
56
+ const context = await auth.$context;
57
+ const membership = await context?.adapter?.findOne({
58
+ model: "member",
59
+ where: [
60
+ { field: "userId", value: userId },
61
+ { field: "organizationId", value: organizationId },
62
+ ],
63
+ });
64
+ return membership?.role;
65
+ }
66
+ catch {
67
+ // A membership that cannot be read is not a role. Treated as customer, as
68
+ // the plugin-endpoint version was, so this stays a performance change.
69
+ return undefined;
70
+ }
71
+ }
34
72
  /** Resolve a better-auth session and its porulle organization permissions. */
35
73
  export async function resolveActor(headers, auth, config, request = new Request("http://localhost", { headers })) {
36
74
  let session;
@@ -52,32 +90,10 @@ export async function resolveActor(headers, auth, config, request = new Request(
52
90
  const defaultOrgId = config.auth?.defaultOrganizationId ?? DEFAULT_ORG_ID;
53
91
  let role = session.session.activeOrganizationRole;
54
92
  let orgId = session.session.activeOrganizationId;
55
- if (!role && auth.api.getFullOrganization) {
56
- try {
57
- const org = await auth.api.getFullOrganization({
58
- query: { organizationId: orgId ?? defaultOrgId },
59
- headers,
60
- });
61
- if (org?.members) {
62
- const membership = org.members.find((m) => m.userId === session.user.id);
63
- if (membership) {
64
- role = membership.role;
65
- orgId = orgId ?? defaultOrgId;
66
- }
67
- }
68
- }
69
- catch {
70
- // fall through — treat as customer
71
- }
72
- }
73
- if (!role && orgId && auth.api.getActiveMemberRole) {
74
- try {
75
- const roleResult = await auth.api.getActiveMemberRole({ headers });
76
- role = roleResult?.role;
77
- }
78
- catch {
79
- // fall through — treat as customer
80
- }
93
+ if (!role) {
94
+ role = await findMembershipRole(auth, session.user.id, orgId ?? defaultOrgId);
95
+ if (role)
96
+ orgId = orgId ?? defaultOrgId;
81
97
  }
82
98
  if (!orgId && config.auth?.storeResolver) {
83
99
  try {
@@ -37,6 +37,13 @@ export declare class CartService {
37
37
  lineItems: CartLineItem[];
38
38
  }>>;
39
39
  addItem(input: AddCartItemInput, actor?: Actor | null, ctx?: TxContext, presentedSecret?: string): Promise<Result<CartLineItem>>;
40
+ /**
41
+ * The unit price for a new cart line, from the pricing step. Refuses — naming the entity and the
42
+ * currency — when no price is configured, when the pricing service is absent, or when resolution
43
+ * fails for any other reason: every one of those is a value the system cannot determine, and the
44
+ * defect this replaces was substituting a literal for exactly that.
45
+ */
46
+ private resolveUnitPrice;
40
47
  removeItem(cartId: string, itemId: string, actor?: Actor | null, ctx?: TxContext, presentedSecret?: string): Promise<Result<void>>;
41
48
  updateQuantity(input: UpdateCartItemInput, actor?: Actor | null, ctx?: TxContext, presentedSecret?: string): Promise<Result<CartLineItem>>;
42
49
  merge(sourceCartId: string, targetCartId: string, actor?: Actor | null, ctx?: TxContext): Promise<Result<void>>;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/cart/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/cart/service.ts"],"names":[],"mappings":"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"}
@@ -191,12 +191,35 @@ export class CartService {
191
191
  item = updated;
192
192
  }
193
193
  else {
194
+ // A new line needs a price, and a price the system cannot determine is refused rather than
195
+ // invented. This used to read `processed.unitPriceSnapshot ?? 1000`: a silent default in a
196
+ // money path, which nothing in the response, the logs or the schema disclosed. An integrator
197
+ // found it by comparing a deployed cart reading 1000 against a catalog priced 14500-22800.
198
+ //
199
+ // A hook still wins when it supplies one, so bespoke pricing keeps its seam; otherwise the
200
+ // pricing step answers — the SAME step `resolveCurrentPrices` uses at checkout, so a cart and
201
+ // its order agree by construction rather than by an integrator remembering to install a hook.
202
+ const currency = processed.currency ?? cart.currency;
203
+ let unitPriceSnapshot = processed.unitPriceSnapshot;
204
+ if (unitPriceSnapshot === undefined) {
205
+ const resolved = await this.resolveUnitPrice({
206
+ entityId: processed.entityId,
207
+ currency,
208
+ quantity,
209
+ ...(processed.variantId != null
210
+ ? { variantId: processed.variantId }
211
+ : {}),
212
+ }, actor ?? null, ctx);
213
+ if (!resolved.ok)
214
+ return resolved;
215
+ unitPriceSnapshot = resolved.value;
216
+ }
194
217
  item = await this.repo.createLineItem({
195
218
  cartId: input.cartId,
196
219
  entityId: processed.entityId,
197
220
  quantity,
198
- unitPriceSnapshot: processed.unitPriceSnapshot ?? 1000,
199
- currency: processed.currency ?? cart.currency,
221
+ unitPriceSnapshot,
222
+ currency,
200
223
  metadata: processed.metadata ?? {},
201
224
  ...(processed.variantId !== undefined
202
225
  ? { variantId: processed.variantId }
@@ -206,6 +229,23 @@ export class CartService {
206
229
  await runAfterHooks(afterHooks, null, item, "addItem", context);
207
230
  return Ok(item);
208
231
  }
232
+ /**
233
+ * The unit price for a new cart line, from the pricing step. Refuses — naming the entity and the
234
+ * currency — when no price is configured, when the pricing service is absent, or when resolution
235
+ * fails for any other reason: every one of those is a value the system cannot determine, and the
236
+ * defect this replaces was substituting a literal for exactly that.
237
+ */
238
+ async resolveUnitPrice(input, actor, ctx) {
239
+ const pricing = this.deps.services.pricing;
240
+ if (typeof pricing?.resolve !== "function") {
241
+ return Err(new CommerceValidationError(`Cannot resolve a unit price for ${input.entityId}: no pricing service is configured.`));
242
+ }
243
+ const resolved = await pricing.resolve(input, actor, ctx);
244
+ if (!resolved.ok) {
245
+ return Err(new CommerceValidationError(`Cannot resolve a unit price for ${input.entityId} (${input.currency}). Configure a price for it, or supply unitPriceSnapshot from a cart.beforeAddItem hook.`));
246
+ }
247
+ return Ok(resolved.value.finalAmount);
248
+ }
209
249
  async removeItem(cartId, itemId, actor, ctx, presentedSecret) {
210
250
  try {
211
251
  assertPermission(actor ?? null, "cart:update");
@@ -13,6 +13,20 @@
13
13
  */
14
14
  import type { DatabaseAdapter } from "../kernel/database/adapter.js";
15
15
  import type { DrizzleDatabase } from "../kernel/database/drizzle-db.js";
16
+ /**
17
+ * Records the SQL statements issued between `start()` and `stop()`.
18
+ *
19
+ * Counting statements is the regression guard for round-trip cost: the queries
20
+ * behind an authenticated request take 0.3 ms of database time between them, so
21
+ * a timing assertion measures the network and tells you nothing you can act on,
22
+ * while a count is stable, fast and names exactly what regressed.
23
+ */
24
+ export interface QueryLog {
25
+ /** Begin recording; clears anything previously recorded. */
26
+ start(): void;
27
+ /** Stop recording and return the statements captured, in order. */
28
+ stop(): string[];
29
+ }
16
30
  /**
17
31
  * Creates a PGlite-backed database adapter for testing.
18
32
  *
@@ -28,5 +42,6 @@ export declare function createPGliteTestAdapter(): Promise<{
28
42
  adapter: DatabaseAdapter;
29
43
  db: DrizzleDatabase;
30
44
  cleanup: () => Promise<void>;
45
+ queryLog: QueryLog;
31
46
  }>;
32
47
  //# sourceMappingURL=create-pglite-adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-pglite-adapter.d.ts","sourceRoot":"","sources":["../../src/test-utils/create-pglite-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAMrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AA0BxE;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC;IACvD,OAAO,EAAE,eAAe,CAAC;IACzB,EAAE,EAAE,eAAe,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC,CAqFD"}
1
+ {"version":3,"file":"create-pglite-adapter.d.ts","sourceRoot":"","sources":["../../src/test-utils/create-pglite-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAMrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAExE;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACvB,4DAA4D;IAC5D,KAAK,IAAI,IAAI,CAAC;IACd,mEAAmE;IACnE,IAAI,IAAI,MAAM,EAAE,CAAC;CAClB;AA0BD;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC;IACvD,OAAO,EAAE,eAAe,CAAC;IACzB,EAAE,EAAE,eAAe,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,QAAQ,EAAE,QAAQ,CAAC;CACpB,CAAC,CA6GD"}
@@ -50,8 +50,32 @@ async function pushCoreSchema(db) {
50
50
  export async function createPGliteTestAdapter() {
51
51
  // Create in-memory PGlite instance
52
52
  const pg = new PGlite();
53
+ // Every statement Drizzle issues passes through this logger, and Better Auth's
54
+ // drizzleAdapter shares this same instance — so a recording covers the auth
55
+ // reads too. That is the point: the only honest regression guard for "this
56
+ // request costs N round trips" is a count of the statements, not a stopwatch.
57
+ const recorded = [];
58
+ let recording = false;
59
+ const queryLog = {
60
+ start() {
61
+ recorded.length = 0;
62
+ recording = true;
63
+ },
64
+ stop() {
65
+ recording = false;
66
+ return [...recorded];
67
+ },
68
+ };
53
69
  // Wrap with Drizzle ORM first (pushSchema needs the Drizzle instance)
54
- const db = drizzle(pg, { schema: fullSchema });
70
+ const db = drizzle(pg, {
71
+ schema: fullSchema,
72
+ logger: {
73
+ logQuery(query) {
74
+ if (recording)
75
+ recorded.push(query);
76
+ },
77
+ },
78
+ });
55
79
  // Push core schema via drizzle-kit/api (no migration files needed)
56
80
  // PgliteDatabase<Schema> and DrizzleDatabase share the same Schema type;
57
81
  // the HKT parameter differs (PgliteQueryResultHKT vs PgQueryResultHKT)
@@ -125,5 +149,5 @@ export async function createPGliteTestAdapter() {
125
149
  // Re-insert default org after truncation (CASCADE wipes it)
126
150
  await ensureDefaultOrg(db);
127
151
  }
128
- return { adapter, db, cleanup };
152
+ return { adapter, db, cleanup, queryLog };
129
153
  }
@@ -1,4 +1,5 @@
1
1
  import type { CommerceConfig } from "../config/types.js";
2
+ import type { QueryLog } from "./create-pglite-adapter.js";
2
3
  export declare function createTestConfig(overrides?: Partial<CommerceConfig>): Promise<CommerceConfig>;
3
4
  /**
4
5
  * Creates a test config backed by PGlite (in-memory PostgreSQL).
@@ -14,5 +15,6 @@ export declare function createTestConfig(overrides?: Partial<CommerceConfig>): P
14
15
  export declare function createPGliteTestConfig(overrides?: Partial<CommerceConfig>): Promise<{
15
16
  config: CommerceConfig;
16
17
  cleanup: () => Promise<void>;
18
+ queryLog: QueryLog;
17
19
  }>;
18
20
  //# sourceMappingURL=create-test-config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-test-config.d.ts","sourceRoot":"","sources":["../../src/test-utils/create-test-config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAgDzD,wBAAsB,gBAAgB,CACpC,SAAS,GAAE,OAAO,CAAC,cAAc,CAAM,GACtC,OAAO,CAAC,cAAc,CAAC,CAqHzB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,GAAE,OAAO,CAAC,cAAc,CAAM,GACtC,OAAO,CAAC;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAUnE"}
1
+ {"version":3,"file":"create-test-config.d.ts","sourceRoot":"","sources":["../../src/test-utils/create-test-config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AA8C3D,wBAAsB,gBAAgB,CACpC,SAAS,GAAE,OAAO,CAAC,cAAc,CAAM,GACtC,OAAO,CAAC,cAAc,CAAC,CAqHzB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,GAAE,OAAO,CAAC,cAAc,CAAM,GACtC,OAAO,CAAC;IACT,MAAM,EAAE,cAAc,CAAC;IACvB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,QAAQ,EAAE,QAAQ,CAAC;CACpB,CAAC,CAUD"}
@@ -169,10 +169,10 @@ export async function createTestConfig(overrides = {}) {
169
169
  */
170
170
  export async function createPGliteTestConfig(overrides = {}) {
171
171
  const { createPGliteTestAdapter } = await import("./create-pglite-adapter.js");
172
- const { adapter, cleanup } = await createPGliteTestAdapter();
172
+ const { adapter, cleanup, queryLog } = await createPGliteTestAdapter();
173
173
  const config = await createTestConfig({
174
174
  databaseAdapter: adapter,
175
175
  ...overrides,
176
176
  });
177
- return { config, cleanup };
177
+ return { config, cleanup, queryLog };
178
178
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/core",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
package/src/auth/actor.ts CHANGED
@@ -4,6 +4,21 @@ import type { AuthInstance } from "./setup.js";
4
4
  import { DEFAULT_ORG_ID } from "./org.js";
5
5
  import { isCredentialRejection } from "./auth-failure.js";
6
6
 
7
+ /**
8
+ * The slice of Better Auth's internal context this file uses. Declared here
9
+ * rather than imported because `$context` is not on the public `Auth` type; if a
10
+ * future Better Auth removes or renames it, `findMembershipRole` degrades to
11
+ * "no role" rather than throwing, and the membership tests fail loudly.
12
+ */
13
+ interface AuthContextLike {
14
+ adapter?: {
15
+ findOne<T>(query: {
16
+ model: string;
17
+ where: { field: string; value: unknown }[];
18
+ }): Promise<T | null>;
19
+ };
20
+ }
21
+
7
22
  export const AUTH_COOKIE_PREFIX = "uc";
8
23
  export const SESSION_COOKIE_NAME = `${AUTH_COOKIE_PREFIX}.session_token`;
9
24
 
@@ -42,6 +57,50 @@ function toIsoString(value: unknown): string | null {
42
57
  return date !== null && Number.isFinite(date.getTime()) ? date.toISOString() : null;
43
58
  }
44
59
 
60
+ /**
61
+ * The caller's role in one organization, by one indexed read of `member`.
62
+ *
63
+ * This used to go through the organization plugin's endpoints, and that cost six
64
+ * statements instead of one. `getFullOrganization` loads the organization row,
65
+ * its invitations and its ENTIRE member list, then scans in JavaScript for a
66
+ * single membership; `getActiveMemberRole` then looks for the same membership
67
+ * again. Both take `headers` and re-resolve the session from them, so each also
68
+ * re-reads `session` and `user`, and the plugin writes `active_organization_id`
69
+ * back to the session row — a write on the hot path of every GET.
70
+ *
71
+ * For a shopper every one of those is a guaranteed miss: a shopper is not a
72
+ * member of the platform organization and never will be. The member-by-
73
+ * organization scan also grows with the member list, so the platform's busiest
74
+ * request got slower as the platform got bigger.
75
+ *
76
+ * The adapter read below is the same query the plugin ended with, issued once
77
+ * and without re-resolving anything. `findOne` returning null IS the answer for
78
+ * a shopper — one miss, done.
79
+ */
80
+ async function findMembershipRole(
81
+ auth: AuthInstance,
82
+ userId: string,
83
+ organizationId: string,
84
+ ): Promise<string | undefined> {
85
+ try {
86
+ const context = await (
87
+ auth as unknown as { $context?: Promise<AuthContextLike> }
88
+ ).$context;
89
+ const membership = await context?.adapter?.findOne<{ role?: string }>({
90
+ model: "member",
91
+ where: [
92
+ { field: "userId", value: userId },
93
+ { field: "organizationId", value: organizationId },
94
+ ],
95
+ });
96
+ return membership?.role;
97
+ } catch {
98
+ // A membership that cannot be read is not a role. Treated as customer, as
99
+ // the plugin-endpoint version was, so this stays a performance change.
100
+ return undefined;
101
+ }
102
+ }
103
+
45
104
  /** Resolve a better-auth session and its porulle organization permissions. */
46
105
  export async function resolveActor(
47
106
  headers: Headers,
@@ -68,35 +127,9 @@ export async function resolveActor(
68
127
  let role = session.session.activeOrganizationRole as string | undefined;
69
128
  let orgId = session.session.activeOrganizationId as string | null;
70
129
 
71
- if (!role && auth.api.getFullOrganization) {
72
- try {
73
- const org = await auth.api.getFullOrganization({
74
- query: { organizationId: orgId ?? defaultOrgId },
75
- headers,
76
- });
77
- if (org?.members) {
78
- const membership = org.members.find(
79
- (m) => m.userId === session.user.id,
80
- );
81
- if (membership) {
82
- role = membership.role;
83
- orgId = orgId ?? defaultOrgId;
84
- }
85
- }
86
- } catch {
87
- // fall through — treat as customer
88
- }
89
- }
90
-
91
- if (!role && orgId && auth.api.getActiveMemberRole) {
92
- try {
93
- const roleResult = await auth.api.getActiveMemberRole({ headers });
94
- role = (roleResult as Record<string, unknown>)?.role as
95
- | string
96
- | undefined;
97
- } catch {
98
- // fall through — treat as customer
99
- }
130
+ if (!role) {
131
+ role = await findMembershipRole(auth, session.user.id, orgId ?? defaultOrgId);
132
+ if (role) orgId = orgId ?? defaultOrgId;
100
133
  }
101
134
 
102
135
  if (!orgId && config.auth?.storeResolver) {
@@ -318,13 +318,39 @@ export class CartService {
318
318
  );
319
319
  item = updated!;
320
320
  } else {
321
+ // A new line needs a price, and a price the system cannot determine is refused rather than
322
+ // invented. This used to read `processed.unitPriceSnapshot ?? 1000`: a silent default in a
323
+ // money path, which nothing in the response, the logs or the schema disclosed. An integrator
324
+ // found it by comparing a deployed cart reading 1000 against a catalog priced 14500-22800.
325
+ //
326
+ // A hook still wins when it supplies one, so bespoke pricing keeps its seam; otherwise the
327
+ // pricing step answers — the SAME step `resolveCurrentPrices` uses at checkout, so a cart and
328
+ // its order agree by construction rather than by an integrator remembering to install a hook.
329
+ const currency = processed.currency ?? cart.currency;
330
+ let unitPriceSnapshot = processed.unitPriceSnapshot;
331
+ if (unitPriceSnapshot === undefined) {
332
+ const resolved = await this.resolveUnitPrice(
333
+ {
334
+ entityId: processed.entityId,
335
+ currency,
336
+ quantity,
337
+ ...(processed.variantId != null
338
+ ? { variantId: processed.variantId }
339
+ : {}),
340
+ },
341
+ actor ?? null,
342
+ ctx,
343
+ );
344
+ if (!resolved.ok) return resolved;
345
+ unitPriceSnapshot = resolved.value;
346
+ }
321
347
  item = await this.repo.createLineItem(
322
348
  {
323
349
  cartId: input.cartId,
324
350
  entityId: processed.entityId,
325
351
  quantity,
326
- unitPriceSnapshot: processed.unitPriceSnapshot ?? 1000,
327
- currency: processed.currency ?? cart.currency,
352
+ unitPriceSnapshot,
353
+ currency,
328
354
  metadata: processed.metadata ?? {},
329
355
  ...(processed.variantId !== undefined
330
356
  ? { variantId: processed.variantId }
@@ -339,6 +365,56 @@ export class CartService {
339
365
  return Ok(item);
340
366
  }
341
367
 
368
+ /**
369
+ * The unit price for a new cart line, from the pricing step. Refuses — naming the entity and the
370
+ * currency — when no price is configured, when the pricing service is absent, or when resolution
371
+ * fails for any other reason: every one of those is a value the system cannot determine, and the
372
+ * defect this replaces was substituting a literal for exactly that.
373
+ */
374
+ private async resolveUnitPrice(
375
+ input: {
376
+ entityId: string;
377
+ currency: string;
378
+ quantity: number;
379
+ variantId?: string;
380
+ },
381
+ actor: Actor | null,
382
+ ctx?: TxContext,
383
+ ): Promise<Result<number>> {
384
+ const pricing = this.deps.services.pricing as
385
+ | {
386
+ resolve(
387
+ params: {
388
+ entityId: string;
389
+ currency: string;
390
+ quantity: number;
391
+ variantId?: string;
392
+ },
393
+ actor?: Actor | null,
394
+ ctx?: TxContext,
395
+ ): Promise<Result<{ finalAmount: number }>>;
396
+ }
397
+ | undefined;
398
+
399
+ if (typeof pricing?.resolve !== "function") {
400
+ return Err(
401
+ new CommerceValidationError(
402
+ `Cannot resolve a unit price for ${input.entityId}: no pricing service is configured.`,
403
+ ),
404
+ );
405
+ }
406
+
407
+ const resolved = await pricing.resolve(input, actor, ctx);
408
+ if (!resolved.ok) {
409
+ return Err(
410
+ new CommerceValidationError(
411
+ `Cannot resolve a unit price for ${input.entityId} (${input.currency}). Configure a price for it, or supply unitPriceSnapshot from a cart.beforeAddItem hook.`,
412
+ ),
413
+ );
414
+ }
415
+ return Ok(resolved.value.finalAmount);
416
+ }
417
+
342
418
  async removeItem(
343
419
  cartId: string,
344
420
  itemId: string,
@@ -24,6 +24,21 @@ import { ensureDefaultOrg } from "../auth/org.js";
24
24
  import * as fullSchema from "../kernel/database/schema.js";
25
25
  import type { DrizzleDatabase } from "../kernel/database/drizzle-db.js";
26
26
 
27
+ /**
28
+ * Records the SQL statements issued between `start()` and `stop()`.
29
+ *
30
+ * Counting statements is the regression guard for round-trip cost: the queries
31
+ * behind an authenticated request take 0.3 ms of database time between them, so
32
+ * a timing assertion measures the network and tells you nothing you can act on,
33
+ * while a count is stable, fast and names exactly what regressed.
34
+ */
35
+ export interface QueryLog {
36
+ /** Begin recording; clears anything previously recorded. */
37
+ start(): void;
38
+ /** Stop recording and return the statements captured, in order. */
39
+ stop(): string[];
40
+ }
41
+
27
42
  // drizzle-kit/api uses CJS internally; createRequire provides ESM compat.
28
43
  const require = createRequire(import.meta.url);
29
44
 
@@ -63,12 +78,37 @@ export async function createPGliteTestAdapter(): Promise<{
63
78
  adapter: DatabaseAdapter;
64
79
  db: DrizzleDatabase;
65
80
  cleanup: () => Promise<void>;
81
+ queryLog: QueryLog;
66
82
  }> {
67
83
  // Create in-memory PGlite instance
68
84
  const pg = new PGlite();
69
85
 
86
+ // Every statement Drizzle issues passes through this logger, and Better Auth's
87
+ // drizzleAdapter shares this same instance — so a recording covers the auth
88
+ // reads too. That is the point: the only honest regression guard for "this
89
+ // request costs N round trips" is a count of the statements, not a stopwatch.
90
+ const recorded: string[] = [];
91
+ let recording = false;
92
+ const queryLog: QueryLog = {
93
+ start() {
94
+ recorded.length = 0;
95
+ recording = true;
96
+ },
97
+ stop() {
98
+ recording = false;
99
+ return [...recorded];
100
+ },
101
+ };
102
+
70
103
  // Wrap with Drizzle ORM first (pushSchema needs the Drizzle instance)
71
- const db = drizzle(pg, { schema: fullSchema });
104
+ const db = drizzle(pg, {
105
+ schema: fullSchema,
106
+ logger: {
107
+ logQuery(query) {
108
+ if (recording) recorded.push(query);
109
+ },
110
+ },
111
+ });
72
112
 
73
113
  // Push core schema via drizzle-kit/api (no migration files needed)
74
114
  // PgliteDatabase<Schema> and DrizzleDatabase share the same Schema type;
@@ -147,5 +187,5 @@ export async function createPGliteTestAdapter(): Promise<{
147
187
  await ensureDefaultOrg(db);
148
188
  }
149
189
 
150
- return { adapter, db, cleanup };
190
+ return { adapter, db, cleanup, queryLog };
151
191
  }
@@ -2,6 +2,7 @@ import { defineConfig } from "../config/define-config.js";
2
2
  import type { CommerceConfig } from "../config/types.js";
3
3
  import { Ok } from "../kernel/result.js";
4
4
  import type { StorageAdapter } from "../modules/media/adapter.js";
5
+ import type { QueryLog } from "./create-pglite-adapter.js";
5
6
 
6
7
  function createInMemoryStorageAdapter(): StorageAdapter {
7
8
  const files = new Map<string, { data: ArrayBuffer; contentType: string }>();
@@ -181,14 +182,18 @@ export async function createTestConfig(
181
182
  */
182
183
  export async function createPGliteTestConfig(
183
184
  overrides: Partial<CommerceConfig> = {},
184
- ): Promise<{ config: CommerceConfig; cleanup: () => Promise<void> }> {
185
+ ): Promise<{
186
+ config: CommerceConfig;
187
+ cleanup: () => Promise<void>;
188
+ queryLog: QueryLog;
189
+ }> {
185
190
  const { createPGliteTestAdapter } = await import("./create-pglite-adapter.js");
186
- const { adapter, cleanup } = await createPGliteTestAdapter();
191
+ const { adapter, cleanup, queryLog } = await createPGliteTestAdapter();
187
192
 
188
193
  const config = await createTestConfig({
189
194
  databaseAdapter: adapter,
190
195
  ...overrides,
191
196
  });
192
197
 
193
- return { config, cleanup };
198
+ return { config, cleanup, queryLog };
194
199
  }