@porulle/core 0.13.0 → 0.15.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.
Files changed (45) hide show
  1. package/dist/auth/actor.d.ts +10 -0
  2. package/dist/auth/actor.d.ts.map +1 -0
  3. package/dist/auth/actor.js +91 -0
  4. package/dist/auth/middleware.d.ts +1 -1
  5. package/dist/auth/middleware.d.ts.map +1 -1
  6. package/dist/auth/middleware.js +5 -89
  7. package/dist/auth/setup.d.ts.map +1 -1
  8. package/dist/auth/setup.js +2 -1
  9. package/dist/config/types.d.ts +4 -2
  10. package/dist/config/types.d.ts.map +1 -1
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -0
  14. package/dist/kernel/database/plugin-types.d.ts +11 -0
  15. package/dist/kernel/database/plugin-types.d.ts.map +1 -1
  16. package/dist/kernel/database/scoped-db.d.ts +12 -0
  17. package/dist/kernel/database/scoped-db.d.ts.map +1 -1
  18. package/dist/kernel/database/scoped-db.js +12 -0
  19. package/dist/kernel/plugin/manifest.d.ts +3 -0
  20. package/dist/kernel/plugin/manifest.d.ts.map +1 -1
  21. package/dist/kernel/plugin/manifest.js +35 -6
  22. package/dist/modules/search/adapter.d.ts +5 -0
  23. package/dist/modules/search/adapter.d.ts.map +1 -1
  24. package/dist/modules/search/module.d.ts.map +1 -1
  25. package/dist/modules/search/module.js +3 -1
  26. package/dist/runtime/kernel-types.d.ts +7 -0
  27. package/dist/runtime/kernel-types.d.ts.map +1 -1
  28. package/dist/runtime/kernel.d.ts +1 -0
  29. package/dist/runtime/kernel.d.ts.map +1 -1
  30. package/dist/runtime/server.d.ts.map +1 -1
  31. package/dist/runtime/server.js +33 -1
  32. package/package.json +3 -3
  33. package/src/auth/actor.ts +110 -0
  34. package/src/auth/middleware.ts +11 -99
  35. package/src/auth/setup.ts +2 -1
  36. package/src/config/types.ts +4 -2
  37. package/src/index.ts +6 -1
  38. package/src/kernel/database/plugin-types.ts +11 -0
  39. package/src/kernel/database/scoped-db.ts +12 -0
  40. package/src/kernel/plugin/manifest.ts +57 -12
  41. package/src/modules/search/adapter.ts +3 -0
  42. package/src/modules/search/module.ts +4 -1
  43. package/src/runtime/kernel-types.ts +9 -0
  44. package/src/runtime/kernel.ts +1 -0
  45. package/src/runtime/server.ts +39 -1
@@ -1,6 +1,7 @@
1
1
  import type { CommerceConfig, PluginPermission } from "../config/types.js";
2
2
  import type { HookRegistry } from "../kernel/hooks/registry.js";
3
3
  import type { DatabaseAdapter } from "../kernel/database/adapter.js";
4
+ import type { PluginDb } from "../kernel/database/plugin-types.js";
4
5
  import { CatalogServiceImpl } from "../modules/catalog/service.js";
5
6
  import { InventoryService } from "../modules/inventory/service.js";
6
7
  import { MediaService } from "../modules/media/service.js";
@@ -63,6 +64,12 @@ export interface Kernel {
63
64
  pluginPermissions: PluginPermission[];
64
65
  logger: ReturnType<typeof createLogger>;
65
66
  }
67
+ export interface ConfigRouteDatabase extends DatabaseAdapter {
68
+ readonly scoped: PluginDb;
69
+ }
70
+ export type ConfigRouteKernel = Omit<Kernel, "database"> & {
71
+ database: ConfigRouteDatabase;
72
+ };
66
73
  export declare const KERNEL_REQUIRED_SERVICE_KEYS: ["catalog", "inventory", "media", "cart", "orders", "payments", "fulfillment", "customers", "webhooks", "analytics", "pricing", "promotions", "tax", "shipping", "search", "settings", "documents", "audit", "compensationFailures"];
67
74
  export declare function assertKernelServicesReady(services: Partial<Kernel["services"]>): asserts services is Kernel["services"];
68
75
  export declare function assertSortedBefore(topo: readonly string[], a: string, b: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"kernel-types.d.ts","sourceRoot":"","sources":["../../src/runtime/kernel-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"kernel-types.d.ts","sourceRoot":"","sources":["../../src/runtime/kernel-types.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import type { CommerceConfig } from "../config/types.js";
2
2
  import { type Kernel, type WebhookDeliveryPayload } from "./kernel-types.js";
3
3
  export type { Kernel, WebhookDeliveryPayload };
4
+ export type { ConfigRouteKernel, ConfigRouteDatabase } from "./kernel-types.js";
4
5
  export { KERNEL_ALL_MODULES, kernelModuleInstantiationOrder, } from "./kernel-modules.js";
5
6
  export declare function createKernel(config: CommerceConfig): Kernel;
6
7
  //# sourceMappingURL=kernel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"kernel.d.ts","sourceRoot":"","sources":["../../src/runtime/kernel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAqBzD,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,sBAAsB,EAC5B,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;AAC/C,OAAO,EACL,kBAAkB,EAClB,8BAA8B,GAC/B,MAAM,qBAAqB,CAAC;AAE7B,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CA0I3D"}
1
+ {"version":3,"file":"kernel.d.ts","sourceRoot":"","sources":["../../src/runtime/kernel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAqBzD,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,sBAAsB,EAC5B,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EACL,kBAAkB,EAClB,8BAA8B,GAC/B,MAAM,qBAAqB,CAAC;AAE7B,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CA0I3D"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/runtime/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAQhD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AASzD,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAMtE,KAAK,SAAS,GAAG;IACf,SAAS,EAAE;QACT,IAAI,EAAE,YAAY,CAAC;QACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAcvC;AAeD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,cAAc;;;;;sBAiWxB,MAAM,UAAU,MAAM;GA6FtD"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/runtime/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAQhD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAUzD,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAStE,KAAK,SAAS,GAAG;IACf,SAAS,EAAE;QACT,IAAI,EAAE,YAAY,CAAC;QACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAcvC;AAeD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,cAAc;;;;;sBAkXxB,MAAM,UAAU,MAAM;GA8GtD"}
@@ -7,6 +7,7 @@ import { rateLimiter } from "hono-rate-limiter";
7
7
  import { createHash } from "node:crypto";
8
8
  import { createClientIpResolver } from "./client-ip.js";
9
9
  import { authMiddleware } from "../auth/middleware.js";
10
+ import { resolveOrgIdForCommerce } from "../auth/org.js";
10
11
  import { organizationGuard } from "../auth/organization-guard.js";
11
12
  import { CommerceCsrfError } from "../kernel/errors.js";
12
13
  import { createRestRoutes } from "../interfaces/rest/index.js";
@@ -16,6 +17,9 @@ import { rewriteCommerceAliasRequest } from "./url-alias-rewrite.js";
16
17
  import { mapErrorToResponse } from "../kernel/error-mapper.js";
17
18
  import { assertRouteCoverage } from "../interfaces/rest/route-coverage.js";
18
19
  import { markRoutePermissionGuard } from "../interfaces/rest/utils.js";
20
+ import { isStrictOrgResolution } from "../auth/strict-org-resolution.js";
21
+ import { runWithPluginDatabaseScope } from "../kernel/database/plugin-db-context.js";
22
+ import { buildConfigRoutesKernel } from "../kernel/plugin/manifest.js";
19
23
  /**
20
24
  * Returns true if running in a Node-like runtime that supports
21
25
  * `process.on()` + `process.exit()`. Edge runtimes (Cloudflare Workers,
@@ -212,10 +216,16 @@ export async function createServer(config) {
212
216
  const signInEmailRateKeyCache = new WeakMap();
213
217
  const getClientIp = createClientIpResolver(config);
214
218
  const keyGenerator = getClientIp;
219
+ app.use("/api/auth/get-session", rateLimiter({
220
+ windowMs: 60 * 1000,
221
+ limit: config.rateLimits?.session ?? 120,
222
+ keyGenerator,
223
+ }));
215
224
  app.use("/api/auth/*", rateLimiter({
216
225
  windowMs: 60 * 1000,
217
226
  limit: config.rateLimits?.auth ?? 10,
218
227
  keyGenerator,
228
+ skip: (c) => c.req.path === "/api/auth/get-session",
219
229
  }));
220
230
  app.use("/api/auth/sign-in/email", async (c, next) => {
221
231
  if (c.req.method !== "POST") {
@@ -280,6 +290,17 @@ export async function createServer(config) {
280
290
  return auth.handler(c.req.raw);
281
291
  });
282
292
  app.use("*", authMiddleware(auth, config));
293
+ app.use("*", async (c, next) => {
294
+ let organizationId;
295
+ try {
296
+ organizationId = resolveOrgIdForCommerce(c.get("actor"), config);
297
+ }
298
+ catch {
299
+ await next();
300
+ return;
301
+ }
302
+ await runWithPluginDatabaseScope(organizationId, next);
303
+ });
283
304
  app.use("*", async (c, next) => {
284
305
  c.set("auth", auth);
285
306
  await next();
@@ -296,7 +317,7 @@ export async function createServer(config) {
296
317
  // body-limit stack apply (all are scoped to /api/*).
297
318
  app.route("/api/me", createCustomerPortalRoutes(kernel));
298
319
  if (config.routes) {
299
- config.routes(app, kernel, auth);
320
+ config.routes(app, buildConfigRoutesKernel(kernel), auth);
300
321
  }
301
322
  // OpenAPI spec — disabled in production unless explicitly enabled
302
323
  const exposeSpec = config.exposeOpenApiSpec ?? !isProd;
@@ -420,6 +441,17 @@ export async function createServer(config) {
420
441
  "or set config.jobs.autorun.enabled = true for in-process polling.");
421
442
  }
422
443
  }
444
+ const defaultOrganizationId = config.auth?.defaultOrganizationId;
445
+ const hasDefaultOrganization = typeof defaultOrganizationId === "string" && defaultOrganizationId.trim().length > 0;
446
+ if (isStrictOrgResolution(config) &&
447
+ !hasDefaultOrganization &&
448
+ !config.auth?.storeResolver) {
449
+ throw new Error("Cannot resolve an organization for a request with no actor. Set " +
450
+ "auth.defaultOrganizationId for a single-tenant deployment, or " +
451
+ "auth.storeResolver for a multi-tenant one. To keep the previous " +
452
+ "permissive behaviour during a migration, set " +
453
+ "auth.strictOrgResolution: false (or STRICT_ORG_RESOLUTION=false).");
454
+ }
423
455
  assertRouteCoverage(app);
424
456
  const dispatchFetch = app.fetch.bind(app);
425
457
  app.fetch = ((input, env, executionCtx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/core",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -56,8 +56,8 @@
56
56
  "eslint": "^9.39.1",
57
57
  "typescript": "5.9.2",
58
58
  "vitest": "^3.2.4",
59
- "@porulle/eslint-config": "0.1.0",
60
- "@porulle/typescript-config": "0.1.0"
59
+ "@porulle/typescript-config": "0.1.0",
60
+ "@porulle/eslint-config": "0.1.0"
61
61
  },
62
62
  "publishConfig": {
63
63
  "access": "public"
@@ -0,0 +1,110 @@
1
+ import type { AuthSessionLike, CommerceConfig } from "../config/types.js";
2
+ import type { Actor } from "./types.js";
3
+ import type { AuthInstance } from "./setup.js";
4
+ import { DEFAULT_ORG_ID } from "./org.js";
5
+
6
+ export const AUTH_COOKIE_PREFIX = "uc";
7
+ export const SESSION_COOKIE_NAME = `${AUTH_COOKIE_PREFIX}.session_token`;
8
+
9
+ export const DEFAULT_CUSTOMER_PERMISSIONS = [
10
+ "catalog:read",
11
+ "cart:create",
12
+ "cart:read",
13
+ "cart:update",
14
+ "orders:create",
15
+ "orders:read:own",
16
+ "customers:read:self",
17
+ "customers:update:self",
18
+ ] as const;
19
+
20
+ export function getCustomerPermissions(config: CommerceConfig): string[] {
21
+ return config.auth?.customerPermissions ?? [...DEFAULT_CUSTOMER_PERMISSIONS];
22
+ }
23
+
24
+ function resolvePermissions(
25
+ session: AuthSessionLike,
26
+ config: CommerceConfig,
27
+ ): string[] {
28
+ const role = session.session.activeOrganizationRole;
29
+ if (!role) return getCustomerPermissions(config);
30
+ const roleConfig = config.auth?.roles?.[role];
31
+ return roleConfig ? roleConfig.permissions : [];
32
+ }
33
+
34
+ /** Resolve a better-auth session and its porulle organization permissions. */
35
+ export async function resolveActor(
36
+ headers: Headers,
37
+ auth: AuthInstance,
38
+ config: CommerceConfig,
39
+ request: Request = new Request("http://localhost", { headers }),
40
+ ): Promise<Actor | null> {
41
+ let session: AuthSessionLike | null;
42
+ try {
43
+ session = (await auth.api.getSession({
44
+ headers,
45
+ })) as AuthSessionLike | null;
46
+ } catch {
47
+ return null;
48
+ }
49
+
50
+ if (!session) return null;
51
+
52
+ const defaultOrgId = config.auth?.defaultOrganizationId ?? DEFAULT_ORG_ID;
53
+ let role = session.session.activeOrganizationRole as string | undefined;
54
+ let orgId = session.session.activeOrganizationId as string | null;
55
+
56
+ if (!role && auth.api.getFullOrganization) {
57
+ try {
58
+ const org = await auth.api.getFullOrganization({
59
+ query: { organizationId: orgId ?? defaultOrgId },
60
+ headers,
61
+ });
62
+ if (org?.members) {
63
+ const membership = org.members.find(
64
+ (m) => m.userId === session.user.id,
65
+ );
66
+ if (membership) {
67
+ role = membership.role;
68
+ orgId = orgId ?? defaultOrgId;
69
+ }
70
+ }
71
+ } catch {
72
+ // fall through — treat as customer
73
+ }
74
+ }
75
+
76
+ if (!role && orgId && auth.api.getActiveMemberRole) {
77
+ try {
78
+ const roleResult = await auth.api.getActiveMemberRole({ headers });
79
+ role = (roleResult as Record<string, unknown>)?.role as
80
+ | string
81
+ | undefined;
82
+ } catch {
83
+ // fall through — treat as customer
84
+ }
85
+ }
86
+
87
+ if (!orgId && config.auth?.storeResolver) {
88
+ try {
89
+ const resolved = await config.auth.storeResolver(request);
90
+ if (resolved) orgId = resolved;
91
+ } catch {
92
+ // fall through — use defaultOrgId
93
+ }
94
+ }
95
+
96
+ const enrichedSession = {
97
+ ...session,
98
+ session: { ...session.session, activeOrganizationRole: role ?? null },
99
+ };
100
+ return {
101
+ type: "user",
102
+ userId: session.user.id,
103
+ email: session.user.email ?? null,
104
+ name: session.user.name ?? "User",
105
+ vendorId: session.user.vendorId ?? null,
106
+ organizationId: orgId ?? defaultOrgId,
107
+ role: role ?? "customer",
108
+ permissions: resolvePermissions(enrichedSession, config),
109
+ } satisfies Actor;
110
+ }
@@ -1,7 +1,8 @@
1
1
  import type { MiddlewareHandler } from "hono";
2
- import type { AuthSessionLike, CommerceConfig } from "../config/types.js";
2
+ import type { CommerceConfig } from "../config/types.js";
3
3
  import type { Actor } from "./types.js";
4
4
  import type { AuthInstance } from "./setup.js";
5
+ import { getCustomerPermissions, resolveActor } from "./actor.js";
5
6
  import { DEFAULT_ORG_ID } from "./org.js";
6
7
  import { isStrictOrgResolution } from "./strict-org-resolution.js";
7
8
 
@@ -12,37 +13,11 @@ function emptyToNull(value: string | null | undefined): string | null {
12
13
  // Exported so the storefront contract can be pinned by a test: an anonymous
13
14
  // visitor resolved by `storeResolver` gets these permissions, so dropping
14
15
  // `catalog:read` here would 401 every public storefront read.
15
-
16
- export const DEFAULT_CUSTOMER_PERMISSIONS = [
17
- "catalog:read",
18
- "cart:create",
19
- "cart:read",
20
- "cart:update",
21
- "orders:create",
22
- "orders:read:own",
23
- "customers:read:self",
24
- "customers:update:self",
25
- ] as const;
26
-
27
- function getCustomerPermissions(config: CommerceConfig): string[] {
28
- return config.auth?.customerPermissions ?? [...DEFAULT_CUSTOMER_PERMISSIONS];
29
- }
16
+ export { DEFAULT_CUSTOMER_PERMISSIONS } from "./actor.js";
30
17
 
31
18
  const LEGACY_STORE_RESOLVER_WARN_COOLDOWN_MS = 60_000;
32
19
  let lastLegacyStoreResolverWarnAt = 0;
33
20
 
34
- function resolvePermissions(
35
- session: AuthSessionLike,
36
- config: CommerceConfig,
37
- ): string[] {
38
- const role = session.session.activeOrganizationRole;
39
- if (!role) {
40
- return getCustomerPermissions(config);
41
- }
42
- const roleConfig = config.auth?.roles?.[role];
43
- return roleConfig ? roleConfig.permissions : [];
44
- }
45
-
46
21
  export function authMiddleware(
47
22
  auth: AuthInstance,
48
23
  config: CommerceConfig,
@@ -68,77 +43,14 @@ export function authMiddleware(
68
43
  }
69
44
  }
70
45
 
71
- const session = (await auth.api.getSession({
72
- headers: c.req.raw.headers,
73
- })) as AuthSessionLike | null;
74
-
75
- if (session) {
76
- // Better Auth's session stores activeOrganizationId, but often not the role.
77
- // For single-store apps (org_default), users may never call set-active,
78
- // so activeOrganizationId can be null even for valid org members.
79
- let role = session.session.activeOrganizationRole as string | undefined;
80
- let orgId = session.session.activeOrganizationId as string | null;
81
-
82
- // If no active org, try to resolve the user's membership in org_default.
83
- // This handles the common case where the user is a member but hasn't
84
- // called organization/set-active (single-store apps, seed scripts, tests).
85
- if (!role && auth.api.getFullOrganization) {
86
- try {
87
- const org = await auth.api.getFullOrganization({
88
- query: { organizationId: orgId ?? defaultOrgId },
89
- headers: c.req.raw.headers,
90
- });
91
- if (org?.members) {
92
- const membership = org.members.find(
93
- (m) => m.userId === session.user.id,
94
- );
95
- if (membership) {
96
- role = membership.role;
97
- orgId = orgId ?? defaultOrgId;
98
- }
99
- }
100
- } catch {
101
- // fall through — treat as customer
102
- }
103
- }
104
-
105
- // Also try getActiveMemberRole if active org is set
106
- if (!role && orgId && auth.api.getActiveMemberRole) {
107
- try {
108
- const roleResult = await auth.api.getActiveMemberRole({
109
- headers: c.req.raw.headers,
110
- });
111
- role = (roleResult as Record<string, unknown>)?.role as string | undefined;
112
- } catch {
113
- // fall through — treat as customer
114
- }
115
- }
116
-
117
- // For customers without org membership, resolve the store from the request.
118
- // This enables multi-store SaaS where each storefront is a different org.
119
- if (!orgId && config.auth?.storeResolver) {
120
- try {
121
- const resolved = await config.auth.storeResolver(c.req.raw);
122
- if (resolved) orgId = resolved;
123
- } catch {
124
- // fall through — use defaultOrgId
125
- }
126
- }
127
-
128
- const enrichedSession = {
129
- ...session,
130
- session: { ...session.session, activeOrganizationRole: role ?? null },
131
- };
132
- c.set("actor", {
133
- type: "user",
134
- userId: session.user.id,
135
- email: session.user.email ?? null,
136
- name: session.user.name ?? "User",
137
- vendorId: session.user.vendorId ?? null,
138
- organizationId: orgId ?? defaultOrgId,
139
- role: role ?? "customer",
140
- permissions: resolvePermissions(enrichedSession, config),
141
- } satisfies Actor);
46
+ const actor = await resolveActor(
47
+ c.req.raw.headers,
48
+ auth,
49
+ config,
50
+ c.req.raw,
51
+ );
52
+ if (actor) {
53
+ c.set("actor", actor);
142
54
  await next();
143
55
  return;
144
56
  }
package/src/auth/setup.ts CHANGED
@@ -6,6 +6,7 @@ import { organization, twoFactor, phoneNumber, jwt, bearer } from "better-auth/p
6
6
  import type { CommerceConfig } from "../config/types.js";
7
7
  import type { DatabaseAdapter } from "../kernel/database/adapter.js";
8
8
  import * as authSchema from "./auth-schema.js";
9
+ import { AUTH_COOKIE_PREFIX } from "./actor.js";
9
10
 
10
11
  type BetterAuthDbProvider = "pg";
11
12
 
@@ -178,7 +179,7 @@ export function createAuth(
178
179
  },
179
180
  },
180
181
  advanced: {
181
- cookiePrefix: "uc",
182
+ cookiePrefix: AUTH_COOKIE_PREFIX,
182
183
  useSecureCookies: process.env.NODE_ENV === "production",
183
184
  // VAPT r2 (pi) finding: SameSite was never set anywhere. Without it,
184
185
  // browsers default to Lax for new cookies, but the explicit setting
@@ -3,7 +3,7 @@ import type { Actor } from "../auth/types.js";
3
3
  import type { BeforeHook, AfterHook } from "../kernel/hooks/types.js";
4
4
  import type { PaymentAdapter } from "../modules/payments/adapter.js";
5
5
  import type { StorageAdapter } from "../modules/media/adapter.js";
6
- import type { Kernel } from "../runtime/kernel.js";
6
+ import type { ConfigRouteKernel } from "../runtime/kernel.js";
7
7
  import type { AuthInstance } from "../auth/setup.js";
8
8
  import type { DatabaseAdapter } from "../kernel/database/adapter.js";
9
9
  import type { TaxAdapter } from "../modules/tax/adapter.js";
@@ -439,7 +439,7 @@ export interface CommerceConfig {
439
439
  * third argument so integrations can mint/revoke API keys or call
440
440
  * `auth.api.*` without module-global holder shims.
441
441
  */
442
- routes?: (app: Hono<any>, kernel: Kernel, auth?: AuthInstance) => void;
442
+ routes?: (app: Hono<any>, kernel: ConfigRouteKernel, auth?: AuthInstance) => void;
443
443
  /** Log level for structured logging. Default: "info". */
444
444
  logLevel?: "fatal" | "error" | "warn" | "info" | "debug" | "trace";
445
445
  /**
@@ -453,6 +453,8 @@ export interface CommerceConfig {
453
453
  api?: number;
454
454
  /** Requests per minute for auth endpoints. Default: 10. */
455
455
  auth?: number;
456
+ /** Requests per minute for session reads. Default: 120. */
457
+ session?: number;
456
458
  /** Requests per minute for checkout. Default: 5. */
457
459
  checkout?: number;
458
460
  /** Attempts per 15 minutes for sign-in per email. Default: 10. */
package/src/index.ts CHANGED
@@ -26,12 +26,17 @@ export { createLogger } from "./runtime/logger.js";
26
26
  export type { Logger as PinoLogger } from "./runtime/logger.js";
27
27
  export { setupGracefulShutdown } from "./runtime/shutdown.js";
28
28
  export { createKernel } from "./runtime/kernel.js";
29
- export type { Kernel } from "./runtime/kernel.js";
29
+ export type { Kernel, ConfigRouteKernel, ConfigRouteDatabase } from "./runtime/kernel.js";
30
30
  // Test utilities moved to @porulle/core/testing sub-path export.
31
31
  // Import from "@porulle/core/testing" instead of "@porulle/core".
32
32
  // This avoids pulling drizzle-kit/tsx/esbuild into Turbopack's bundle graph.
33
33
 
34
34
  export type { Actor } from "./auth/types.js";
35
+ export {
36
+ AUTH_COOKIE_PREFIX,
37
+ resolveActor,
38
+ SESSION_COOKIE_NAME,
39
+ } from "./auth/actor.js";
35
40
  export { resolveOrgId, resolveOrgIdForCommerce, ensureDefaultOrg, DEFAULT_ORG_ID } from "./auth/org.js";
36
41
  export { createSystemActor } from "./auth/system-actor.js";
37
42
  export { OrganizationService } from "./modules/organization/service.js";
@@ -13,6 +13,17 @@ import type { PgDatabase, PgQueryResultHKT } from "drizzle-orm/pg-core";
13
13
  /**
14
14
  * Database instance type for plugin services.
15
15
  * This is the Drizzle PgDatabase with an opaque schema record.
16
+ *
17
+ * Tenant scoping depends on which handle you were given, not on this type.
18
+ * `PluginContext.database.db` is wrapped so that `insert`, `select`, `update`
19
+ * and `delete` on any table with an `organizationId` column are constrained to
20
+ * the request's organization. `PluginContext.database.unscoped` and the kernel
21
+ * handle passed to `config.routes` are not wrapped at all.
22
+ *
23
+ * `execute()` is never scoped, on any handle — see the scope-boundary note in
24
+ * `scoped-db.ts`. When you drop to raw SQL, filter by organization yourself and
25
+ * interpolate values as bind parameters (`sql`... ${value}`) rather than
26
+ * through `sql.raw`.
16
27
  */
17
28
  export type PluginDb = PgDatabase<PgQueryResultHKT, Record<string, unknown>>;
18
29
 
@@ -9,6 +9,18 @@
9
9
  * Plugin route handlers receive this scoped db via PluginContext.database.db
10
10
  * (organization resolved per operation from AsyncLocalStorage when used from
11
11
  * plugin routes) or a fixed string / getter from router() / tests.
12
+ *
13
+ * SCOPE BOUNDARY — what this does NOT cover:
14
+ *
15
+ * Only `insert`, `select`, `update` and `delete` are wrapped. `execute()` is
16
+ * deliberately outside the boundary: injecting a tenant predicate into
17
+ * arbitrary SQL would mean parsing it, and a parser that is wrong once is worse
18
+ * than no guarantee at all. So `db.execute(sql`...`)` on a scoped handle is
19
+ * exactly as unscoped as one on the raw handle — which is the surprising part,
20
+ * because the caller reached for the safe object and still has to filter by
21
+ * organization themselves.
22
+ *
23
+ * Adding a fifth wrapped method is fine. Adding a raw-SQL rewriter is not.
12
24
  */
13
25
 
14
26
  import { and, eq, getTableColumns } from "drizzle-orm";
@@ -9,7 +9,7 @@ import {
9
9
  getPluginDatabaseScopeOrganizationId,
10
10
  runWithPluginDatabaseScope,
11
11
  } from "../database/plugin-db-context.js";
12
- import type { Kernel } from "../../runtime/kernel.js";
12
+ import type { ConfigRouteKernel, Kernel } from "../../runtime/kernel.js";
13
13
  import type {
14
14
  CommerceConfig,
15
15
  CommercePlugin,
@@ -73,8 +73,12 @@ export interface PluginContext {
73
73
 
74
74
  const UNSCOPED_WARN_COOLDOWN_MS = 60_000;
75
75
  const UNSCOPED_WARN_EVERY_N = 100;
76
+ const configRouteRawDatabases = new WeakMap<object, DatabaseAdapter>();
76
77
 
77
- function createRateLimitedUnscopedAccessWarning(logger: PluginLogger): () => void {
78
+ export function createRateLimitedUnscopedAccessWarning(
79
+ logger: Pick<PluginLogger, "warn">,
80
+ message: string,
81
+ ): () => void {
78
82
  let lastWarnAt = 0;
79
83
  let accessCount = 0;
80
84
  return () => {
@@ -84,24 +88,64 @@ function createRateLimitedUnscopedAccessWarning(logger: PluginLogger): () => voi
84
88
  return;
85
89
  }
86
90
  lastWarnAt = now;
87
- logger.warn(
88
- "[plugin:database] PluginContext.database.unscoped is deprecated (MT-4 escape hatch); prefer ctx.database.db for tenant-scoped access.",
89
- );
91
+ logger.warn(message);
90
92
  };
91
93
  }
92
94
 
95
+ function getPluginDatabaseOrganizationId(commerceConfig: CommerceConfig): string {
96
+ return (
97
+ getPluginDatabaseScopeOrganizationId() ??
98
+ resolveOrgIdForCommerce(null, commerceConfig)
99
+ );
100
+ }
101
+
102
+ function getRawDatabaseForConfigRoutes(database: DatabaseAdapter): DatabaseAdapter {
103
+ if (database && typeof database === "object") {
104
+ return configRouteRawDatabases.get(database) ?? database;
105
+ }
106
+ return database;
107
+ }
108
+
109
+ export function buildConfigRoutesKernel(kernel: Kernel): ConfigRouteKernel {
110
+ const rawDatabase = getRawDatabaseForConfigRoutes(kernel.database);
111
+ const rawDb = rawDatabase.db as PluginDb;
112
+ const scopedDb = createScopedDb(
113
+ rawDb,
114
+ () => getPluginDatabaseOrganizationId(kernel.config),
115
+ );
116
+ const warnUnscoped = createRateLimitedUnscopedAccessWarning(
117
+ kernel.logger,
118
+ "[config:database] kernel.database.db is unscoped; prefer kernel.database.scoped for tenant-scoped access.",
119
+ );
120
+
121
+ const database: ConfigRouteKernel["database"] = {
122
+ ...rawDatabase,
123
+ get db(): unknown {
124
+ warnUnscoped();
125
+ return rawDatabase.db;
126
+ },
127
+ get scoped(): PluginDb {
128
+ return scopedDb;
129
+ },
130
+ };
131
+ configRouteRawDatabases.set(database, rawDatabase);
132
+
133
+ return { ...kernel, database };
134
+ }
135
+
93
136
  function buildPluginContextDatabase(
94
137
  rawDb: PluginDb,
95
138
  transactionFn: PluginContext["database"]["transaction"],
96
139
  commerceConfig: CommerceConfig,
97
140
  logger: PluginLogger,
98
141
  ): PluginContext["database"] {
99
- const orgGetter = () =>
100
- getPluginDatabaseScopeOrganizationId() ??
101
- resolveOrgIdForCommerce(null, commerceConfig);
142
+ const orgGetter = () => getPluginDatabaseOrganizationId(commerceConfig);
102
143
 
103
144
  const scopedDb = createScopedDb(rawDb, orgGetter);
104
- const warnUnscoped = createRateLimitedUnscopedAccessWarning(logger);
145
+ const warnUnscoped = createRateLimitedUnscopedAccessWarning(
146
+ logger,
147
+ "[plugin:database] PluginContext.database.unscoped is deprecated (MT-4 escape hatch); prefer ctx.database.db for tenant-scoped access.",
148
+ );
105
149
 
106
150
  return {
107
151
  get db(): PluginDb {
@@ -261,7 +305,7 @@ export function defineCommercePlugin(
261
305
  const pluginRoutes = manifest.routes;
262
306
  result = {
263
307
  ...result,
264
- routes: (app: Hono, kernel: Kernel, auth?: unknown) => {
308
+ routes: (app: Hono, kernel: ConfigRouteKernel, auth?: unknown) => {
265
309
  existingRoutes?.(app, kernel, auth as never);
266
310
  const k = kernel as {
267
311
  config: CommerceConfig;
@@ -269,14 +313,15 @@ export function defineCommercePlugin(
269
313
  database: DatabaseAdapter;
270
314
  logger: PluginLogger;
271
315
  };
316
+ const rawDatabase = getRawDatabaseForConfigRoutes(k.database);
272
317
  // Narrow DatabaseAdapter<unknown> → PluginContext.database once here.
273
318
  // All plugin code receives typed PluginDb — no casts downstream.
274
319
  const regs = pluginRoutes({
275
320
  config: k.config,
276
321
  services: k.services,
277
322
  database: buildPluginContextDatabase(
278
- k.database.db as PluginDb,
279
- k.database.transaction as PluginContext["database"]["transaction"],
323
+ rawDatabase.db as PluginDb,
324
+ rawDatabase.transaction as PluginContext["database"]["transaction"],
280
325
  k.config,
281
326
  k.logger,
282
327
  ),
@@ -1,4 +1,5 @@
1
1
  import type { Result } from "../../kernel/result.js";
2
+ import type { PluginDb } from "../../kernel/database/plugin-types.js";
2
3
 
3
4
  export interface SearchDocument {
4
5
  id: string;
@@ -58,6 +59,8 @@ export interface SearchQueryResult {
58
59
 
59
60
  export interface SearchAdapter {
60
61
  readonly providerId: string;
62
+ /** Called once when the search module wires the adapter to the live database. */
63
+ init?(deps: { db: PluginDb }): void;
61
64
  index(documents: SearchDocument[]): Promise<Result<void>>;
62
65
  remove(ids: string[]): Promise<Result<void>>;
63
66
  search(params: SearchQueryParams): Promise<Result<SearchQueryResult>>;
@@ -1,4 +1,5 @@
1
1
  import type { CommerceConfig } from "../../config/types.js";
2
+ import type { PluginDb } from "../../kernel/database/plugin-types.js";
2
3
  import { defineModule } from "../../kernel/module/index.js";
3
4
  import type { CatalogServiceImpl } from "../catalog/service.js";
4
5
  import { SearchService } from "./service.js";
@@ -14,12 +15,14 @@ export const searchModule = defineModule<Record<string, never>, SearchService, S
14
15
  schema: () => ({}),
15
16
  service: (deps) => {
16
17
  const config = deps.config as CommerceConfig;
18
+ const adapter = config.search?.adapter;
19
+ adapter?.init?.({ db: deps.db.db as PluginDb });
17
20
  return new SearchService({
18
21
  catalogRepository: deps.services.catalog.repository,
19
22
  resolveEntityFieldDefinitions: deps.services.catalog.resolveEntityFieldDefinitions.bind(deps.services.catalog),
20
23
  config,
21
24
  ...(config.entities ? { entities: config.entities } : {}),
22
- ...(config.search?.adapter ? { adapter: config.search.adapter } : {}),
25
+ ...(adapter ? { adapter } : {}),
23
26
  ...(config.search?.defaultFacets
24
27
  ? { defaultFacets: config.search.defaultFacets }
25
28
  : {}),