@unifiedcommerce/core 0.1.0 → 0.2.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 (245) hide show
  1. package/dist/auth/setup.d.ts.map +1 -1
  2. package/dist/auth/setup.js +8 -3
  3. package/dist/config/types.d.ts +3 -1
  4. package/dist/config/types.d.ts.map +1 -1
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -0
  8. package/dist/interfaces/mcp/server.d.ts +3 -5
  9. package/dist/interfaces/mcp/server.d.ts.map +1 -1
  10. package/dist/interfaces/mcp/server.js +25 -510
  11. package/dist/interfaces/mcp/tool-builder.d.ts +120 -0
  12. package/dist/interfaces/mcp/tool-builder.d.ts.map +1 -0
  13. package/dist/interfaces/mcp/tool-builder.js +224 -0
  14. package/dist/interfaces/mcp/tools/analytics.d.ts +42 -0
  15. package/dist/interfaces/mcp/tools/analytics.d.ts.map +1 -0
  16. package/dist/interfaces/mcp/tools/analytics.js +70 -0
  17. package/dist/interfaces/mcp/tools/cart.d.ts +14 -0
  18. package/dist/interfaces/mcp/tools/cart.d.ts.map +1 -0
  19. package/dist/interfaces/mcp/tools/cart.js +47 -0
  20. package/dist/interfaces/mcp/tools/catalog.d.ts +53 -0
  21. package/dist/interfaces/mcp/tools/catalog.d.ts.map +1 -0
  22. package/dist/interfaces/mcp/tools/catalog.js +284 -0
  23. package/dist/interfaces/mcp/tools/index.d.ts +3 -0
  24. package/dist/interfaces/mcp/tools/index.d.ts.map +1 -0
  25. package/dist/interfaces/mcp/tools/index.js +20 -0
  26. package/dist/interfaces/mcp/tools/inventory.d.ts +27 -0
  27. package/dist/interfaces/mcp/tools/inventory.d.ts.map +1 -0
  28. package/dist/interfaces/mcp/tools/inventory.js +143 -0
  29. package/dist/interfaces/mcp/tools/orders.d.ts +18 -0
  30. package/dist/interfaces/mcp/tools/orders.d.ts.map +1 -0
  31. package/dist/interfaces/mcp/tools/orders.js +82 -0
  32. package/dist/interfaces/mcp/tools/pricing.d.ts +29 -0
  33. package/dist/interfaces/mcp/tools/pricing.d.ts.map +1 -0
  34. package/dist/interfaces/mcp/tools/pricing.js +90 -0
  35. package/dist/interfaces/mcp/tools/promotions.d.ts +44 -0
  36. package/dist/interfaces/mcp/tools/promotions.d.ts.map +1 -0
  37. package/dist/interfaces/mcp/tools/promotions.js +109 -0
  38. package/dist/interfaces/mcp/tools/registry.d.ts +32 -0
  39. package/dist/interfaces/mcp/tools/registry.d.ts.map +1 -0
  40. package/dist/interfaces/mcp/tools/registry.js +55 -0
  41. package/dist/interfaces/mcp/tools/search.d.ts +14 -0
  42. package/dist/interfaces/mcp/tools/search.d.ts.map +1 -0
  43. package/dist/interfaces/mcp/tools/search.js +39 -0
  44. package/dist/interfaces/mcp/tools/webhooks.d.ts +15 -0
  45. package/dist/interfaces/mcp/tools/webhooks.d.ts.map +1 -0
  46. package/dist/interfaces/mcp/tools/webhooks.js +48 -0
  47. package/dist/interfaces/mcp/transport.d.ts +17 -2
  48. package/dist/interfaces/mcp/transport.d.ts.map +1 -1
  49. package/dist/interfaces/mcp/transport.js +91 -44
  50. package/dist/interfaces/rest/router.d.ts.map +1 -1
  51. package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
  52. package/dist/interfaces/rest/routes/checkout.js +1 -1
  53. package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -1
  54. package/dist/interfaces/rest/routes/promotions.js +3 -2
  55. package/dist/kernel/database/adapter.d.ts +8 -0
  56. package/dist/kernel/database/adapter.d.ts.map +1 -1
  57. package/dist/kernel/factory/repository-factory.d.ts.map +1 -1
  58. package/dist/kernel/factory/repository-factory.js +3 -1
  59. package/dist/kernel/local-api.d.ts.map +1 -1
  60. package/dist/kernel/local-api.js +2 -0
  61. package/dist/kernel/plugin/manifest.d.ts +3 -3
  62. package/dist/kernel/plugin/manifest.d.ts.map +1 -1
  63. package/dist/kernel/plugin/manifest.js +36 -7
  64. package/dist/runtime/kernel.d.ts +1 -2
  65. package/dist/runtime/kernel.d.ts.map +1 -1
  66. package/dist/runtime/kernel.js +16 -8
  67. package/dist/runtime/server.d.ts.map +1 -1
  68. package/dist/runtime/server.js +8 -3
  69. package/dist/test-utils/create-pglite-adapter.d.ts.map +1 -1
  70. package/dist/test-utils/create-pglite-adapter.js +7 -6
  71. package/dist/tsconfig.tsbuildinfo +1 -0
  72. package/package.json +2 -2
  73. package/src/adapters/console-email.ts +0 -43
  74. package/src/auth/access.ts +0 -187
  75. package/src/auth/auth-schema.ts +0 -139
  76. package/src/auth/middleware.ts +0 -161
  77. package/src/auth/org.ts +0 -41
  78. package/src/auth/permissions.ts +0 -28
  79. package/src/auth/setup.ts +0 -169
  80. package/src/auth/system-actor.ts +0 -19
  81. package/src/auth/types.ts +0 -10
  82. package/src/config/defaults.ts +0 -82
  83. package/src/config/define-config.ts +0 -53
  84. package/src/config/types.ts +0 -299
  85. package/src/generated/plugin-capabilities.d.ts +0 -20
  86. package/src/generated/plugin-manifest.ts +0 -23
  87. package/src/generated/plugin-repositories.d.ts +0 -20
  88. package/src/hooks/checkout-completion.ts +0 -262
  89. package/src/hooks/checkout.ts +0 -677
  90. package/src/hooks/order-emails.ts +0 -62
  91. package/src/index.ts +0 -214
  92. package/src/interfaces/mcp/agent-prompt.ts +0 -174
  93. package/src/interfaces/mcp/context-enrichment.ts +0 -177
  94. package/src/interfaces/mcp/server.ts +0 -617
  95. package/src/interfaces/mcp/transport.ts +0 -68
  96. package/src/interfaces/rest/customer-portal.ts +0 -299
  97. package/src/interfaces/rest/index.ts +0 -74
  98. package/src/interfaces/rest/router.ts +0 -334
  99. package/src/interfaces/rest/routes/admin-jobs.ts +0 -58
  100. package/src/interfaces/rest/routes/audit.ts +0 -50
  101. package/src/interfaces/rest/routes/carts.ts +0 -89
  102. package/src/interfaces/rest/routes/catalog.ts +0 -493
  103. package/src/interfaces/rest/routes/checkout.ts +0 -283
  104. package/src/interfaces/rest/routes/inventory.ts +0 -70
  105. package/src/interfaces/rest/routes/media.ts +0 -86
  106. package/src/interfaces/rest/routes/orders.ts +0 -78
  107. package/src/interfaces/rest/routes/payments.ts +0 -60
  108. package/src/interfaces/rest/routes/pricing.ts +0 -57
  109. package/src/interfaces/rest/routes/promotions.ts +0 -92
  110. package/src/interfaces/rest/routes/search.ts +0 -71
  111. package/src/interfaces/rest/routes/webhooks.ts +0 -46
  112. package/src/interfaces/rest/schemas/admin-jobs.ts +0 -40
  113. package/src/interfaces/rest/schemas/audit.ts +0 -46
  114. package/src/interfaces/rest/schemas/carts.ts +0 -125
  115. package/src/interfaces/rest/schemas/catalog.ts +0 -450
  116. package/src/interfaces/rest/schemas/checkout.ts +0 -66
  117. package/src/interfaces/rest/schemas/customer-portal.ts +0 -195
  118. package/src/interfaces/rest/schemas/inventory.ts +0 -138
  119. package/src/interfaces/rest/schemas/media.ts +0 -75
  120. package/src/interfaces/rest/schemas/orders.ts +0 -104
  121. package/src/interfaces/rest/schemas/pricing.ts +0 -80
  122. package/src/interfaces/rest/schemas/promotions.ts +0 -110
  123. package/src/interfaces/rest/schemas/responses.ts +0 -85
  124. package/src/interfaces/rest/schemas/search.ts +0 -58
  125. package/src/interfaces/rest/schemas/shared.ts +0 -62
  126. package/src/interfaces/rest/schemas/webhooks.ts +0 -68
  127. package/src/interfaces/rest/utils.ts +0 -104
  128. package/src/interfaces/rest/webhook-router.ts +0 -50
  129. package/src/kernel/compensation/executor.ts +0 -61
  130. package/src/kernel/compensation/types.ts +0 -26
  131. package/src/kernel/database/adapter.ts +0 -13
  132. package/src/kernel/database/drizzle-db.ts +0 -56
  133. package/src/kernel/database/migrate.ts +0 -76
  134. package/src/kernel/database/plugin-types.ts +0 -34
  135. package/src/kernel/database/schema.ts +0 -49
  136. package/src/kernel/database/scoped-db.ts +0 -68
  137. package/src/kernel/database/tx-context.ts +0 -46
  138. package/src/kernel/error-mapper.ts +0 -15
  139. package/src/kernel/errors.ts +0 -89
  140. package/src/kernel/factory/repository-factory.ts +0 -242
  141. package/src/kernel/hooks/create-context.ts +0 -43
  142. package/src/kernel/hooks/executor.ts +0 -88
  143. package/src/kernel/hooks/registry.ts +0 -74
  144. package/src/kernel/hooks/types.ts +0 -52
  145. package/src/kernel/http-error.ts +0 -44
  146. package/src/kernel/jobs/adapter.ts +0 -36
  147. package/src/kernel/jobs/drizzle-adapter.ts +0 -58
  148. package/src/kernel/jobs/runner.ts +0 -153
  149. package/src/kernel/jobs/schema.ts +0 -46
  150. package/src/kernel/jobs/types.ts +0 -30
  151. package/src/kernel/local-api.ts +0 -185
  152. package/src/kernel/plugin/manifest.ts +0 -253
  153. package/src/kernel/query/executor.ts +0 -184
  154. package/src/kernel/query/registry.ts +0 -46
  155. package/src/kernel/result.ts +0 -33
  156. package/src/kernel/schema/extra-columns.ts +0 -37
  157. package/src/kernel/service-registry.ts +0 -76
  158. package/src/kernel/service-timing.ts +0 -89
  159. package/src/kernel/state-machine/machine.ts +0 -101
  160. package/src/modules/analytics/drizzle-adapter.ts +0 -426
  161. package/src/modules/analytics/hooks.ts +0 -11
  162. package/src/modules/analytics/models.ts +0 -125
  163. package/src/modules/analytics/repository/index.ts +0 -6
  164. package/src/modules/analytics/service.ts +0 -245
  165. package/src/modules/analytics/types.ts +0 -180
  166. package/src/modules/audit/hooks.ts +0 -78
  167. package/src/modules/audit/schema.ts +0 -33
  168. package/src/modules/audit/service.ts +0 -151
  169. package/src/modules/cart/access.ts +0 -27
  170. package/src/modules/cart/matcher.ts +0 -26
  171. package/src/modules/cart/repository/index.ts +0 -234
  172. package/src/modules/cart/schema.ts +0 -42
  173. package/src/modules/cart/schemas.ts +0 -38
  174. package/src/modules/cart/service.ts +0 -541
  175. package/src/modules/catalog/repository/index.ts +0 -772
  176. package/src/modules/catalog/schema.ts +0 -203
  177. package/src/modules/catalog/schemas.ts +0 -104
  178. package/src/modules/catalog/service.ts +0 -1544
  179. package/src/modules/customers/repository/index.ts +0 -327
  180. package/src/modules/customers/schema.ts +0 -64
  181. package/src/modules/customers/service.ts +0 -171
  182. package/src/modules/fulfillment/repository/index.ts +0 -426
  183. package/src/modules/fulfillment/schema.ts +0 -101
  184. package/src/modules/fulfillment/service.ts +0 -555
  185. package/src/modules/fulfillment/types.ts +0 -59
  186. package/src/modules/inventory/repository/index.ts +0 -509
  187. package/src/modules/inventory/schema.ts +0 -94
  188. package/src/modules/inventory/schemas.ts +0 -38
  189. package/src/modules/inventory/service.ts +0 -490
  190. package/src/modules/media/adapter.ts +0 -17
  191. package/src/modules/media/repository/index.ts +0 -274
  192. package/src/modules/media/schema.ts +0 -41
  193. package/src/modules/media/service.ts +0 -151
  194. package/src/modules/orders/repository/index.ts +0 -287
  195. package/src/modules/orders/schema.ts +0 -66
  196. package/src/modules/orders/service.ts +0 -619
  197. package/src/modules/orders/stale-order-cleanup.ts +0 -76
  198. package/src/modules/organization/service.ts +0 -191
  199. package/src/modules/payments/adapter.ts +0 -47
  200. package/src/modules/payments/repository/index.ts +0 -6
  201. package/src/modules/payments/service.ts +0 -107
  202. package/src/modules/pricing/repository/index.ts +0 -291
  203. package/src/modules/pricing/schema.ts +0 -71
  204. package/src/modules/pricing/schemas.ts +0 -38
  205. package/src/modules/pricing/service.ts +0 -494
  206. package/src/modules/promotions/repository/index.ts +0 -325
  207. package/src/modules/promotions/schema.ts +0 -62
  208. package/src/modules/promotions/schemas.ts +0 -38
  209. package/src/modules/promotions/service.ts +0 -598
  210. package/src/modules/search/adapter.ts +0 -57
  211. package/src/modules/search/hooks.ts +0 -12
  212. package/src/modules/search/repository/index.ts +0 -6
  213. package/src/modules/search/service.ts +0 -315
  214. package/src/modules/shipping/calculator.ts +0 -188
  215. package/src/modules/shipping/repository/index.ts +0 -6
  216. package/src/modules/shipping/service.ts +0 -51
  217. package/src/modules/tax/adapter.ts +0 -60
  218. package/src/modules/tax/repository/index.ts +0 -6
  219. package/src/modules/tax/service.ts +0 -53
  220. package/src/modules/webhooks/hook.ts +0 -34
  221. package/src/modules/webhooks/repository/index.ts +0 -278
  222. package/src/modules/webhooks/schema.ts +0 -56
  223. package/src/modules/webhooks/service.ts +0 -117
  224. package/src/modules/webhooks/signing.ts +0 -6
  225. package/src/modules/webhooks/ssrf-guard.ts +0 -71
  226. package/src/modules/webhooks/tasks.ts +0 -52
  227. package/src/modules/webhooks/worker.ts +0 -134
  228. package/src/runtime/commerce.ts +0 -145
  229. package/src/runtime/kernel.ts +0 -419
  230. package/src/runtime/logger.ts +0 -36
  231. package/src/runtime/server.ts +0 -349
  232. package/src/runtime/shutdown.ts +0 -43
  233. package/src/test-utils/create-pglite-adapter.ts +0 -129
  234. package/src/test-utils/create-plugin-test-app.ts +0 -128
  235. package/src/test-utils/create-repository-test-harness.ts +0 -16
  236. package/src/test-utils/create-test-config.ts +0 -190
  237. package/src/test-utils/create-test-kernel.ts +0 -7
  238. package/src/test-utils/create-test-plugin-context.ts +0 -75
  239. package/src/test-utils/rest-api-test-utils.ts +0 -265
  240. package/src/test-utils/test-actors.ts +0 -62
  241. package/src/test-utils/typed-hooks.ts +0 -54
  242. package/src/types/commerce-types.ts +0 -34
  243. package/src/utils/id.ts +0 -3
  244. package/src/utils/logger.ts +0 -18
  245. package/src/utils/pagination.ts +0 -22
@@ -1,34 +0,0 @@
1
- /**
2
- * Canonical database types for plugin service constructors.
3
- *
4
- * Replaces the copy-pasted type definitions in every plugin's types.ts:
5
- * type Db = PgDatabase<PgQueryResultHKT, Record<string, unknown>>
6
- *
7
- * Import from core instead:
8
- * import type { PluginDb, PluginTxFn } from "@unifiedcommerce/core";
9
- */
10
-
11
- import type { PgDatabase, PgQueryResultHKT } from "drizzle-orm/pg-core";
12
-
13
- /**
14
- * Database instance type for plugin services.
15
- * This is the Drizzle PgDatabase with an opaque schema record.
16
- */
17
- export type PluginDb = PgDatabase<PgQueryResultHKT, Record<string, unknown>>;
18
-
19
- /**
20
- * Transaction function type for plugin services that need
21
- * transactional guarantees (e.g., POS transaction complete,
22
- * gift card debit, inventory reservation).
23
- *
24
- * Usage:
25
- * constructor(private db: PluginDb, private txFn: PluginTxFn) {}
26
- *
27
- * async doWork() {
28
- * return this.txFn(async (tx) => {
29
- * await tx.insert(...).values(...);
30
- * await tx.update(...).set(...);
31
- * });
32
- * }
33
- */
34
- export type PluginTxFn = <T>(fn: (tx: PluginDb) => Promise<T>) => Promise<T>;
@@ -1,49 +0,0 @@
1
- /**
2
- * Combined schema barrel for Drizzle.
3
- *
4
- * Single source of truth for all core table definitions. Both drizzle-kit
5
- * (via drizzle.config.ts) and the TypeScript type system consume this file.
6
- *
7
- * Drizzle-kit resolves re-exports, so pointing the config schema option
8
- * at this one file discovers every core table. Plugin schemas live in
9
- * their own packages and are referenced via glob patterns.
10
- */
11
-
12
- // Auth (Better Auth generated tables)
13
- export * from "../../auth/auth-schema.js";
14
-
15
- // Catalog module
16
- export * from "../../modules/catalog/schema.js";
17
-
18
- // Inventory module
19
- export * from "../../modules/inventory/schema.js";
20
-
21
- // Cart module
22
- export * from "../../modules/cart/schema.js";
23
-
24
- // Orders module
25
- export * from "../../modules/orders/schema.js";
26
-
27
- // Customers module
28
- export * from "../../modules/customers/schema.js";
29
-
30
- // Pricing module
31
- export * from "../../modules/pricing/schema.js";
32
-
33
- // Promotions module
34
- export * from "../../modules/promotions/schema.js";
35
-
36
- // Media module
37
- export * from "../../modules/media/schema.js";
38
-
39
- // Webhooks module
40
- export * from "../../modules/webhooks/schema.js";
41
-
42
- // Fulfillment module
43
- export * from "../../modules/fulfillment/schema.js";
44
-
45
- // Jobs (kernel)
46
- export * from "../jobs/schema.js";
47
-
48
- // Audit module
49
- export * from "../../modules/audit/schema.js";
@@ -1,68 +0,0 @@
1
- /**
2
- * Scoped DB Proxy
3
- *
4
- * Wraps a Drizzle PgDatabase instance so that INSERT operations on
5
- * org-scoped tables automatically include the actor's organizationId.
6
- *
7
- * Plugin route handlers receive this scoped db via the router() builder.
8
- * They write normal Drizzle inserts without ever mentioning organizationId:
9
- *
10
- * const [card] = await db.insert(giftCards)
11
- * .values({ code: "ABC", balance: 5000 })
12
- * .returning();
13
- * // organizationId is auto-set from the actor context
14
- *
15
- * SELECT/UPDATE/DELETE scoping is handled at the repository/service layer
16
- * via resolveOrgId(). The proxy focuses on INSERT auto-stamping because
17
- * that is the operation most commonly forgotten by plugin developers.
18
- */
19
-
20
- import { getTableColumns } from "drizzle-orm";
21
- import type { PgTable } from "drizzle-orm/pg-core";
22
-
23
- function isOrgScoped(table: PgTable): boolean {
24
- const columns = getTableColumns(table);
25
- return "organizationId" in columns;
26
- }
27
-
28
- export function createScopedDb<TDb>(rawDb: TDb, organizationId: string): TDb {
29
- if (!rawDb || typeof rawDb !== "object") return rawDb;
30
-
31
- return new Proxy(rawDb as Record<string, unknown>, {
32
- get(target, prop, receiver) {
33
- const value = Reflect.get(target, prop, receiver);
34
-
35
- if (prop === "insert" && typeof value === "function") {
36
- return (table: PgTable) => {
37
- const builder = value.call(target, table);
38
- if (!isOrgScoped(table)) return builder;
39
-
40
- // Wrap .values() to auto-inject organizationId
41
- const originalValues = (builder as Record<string, unknown>).values;
42
- if (typeof originalValues !== "function") return builder;
43
-
44
- return new Proxy(builder as Record<string, unknown>, {
45
- get(t, p, r) {
46
- if (p === "values") {
47
- return (data: unknown) => {
48
- const stamp = (row: Record<string, unknown>) => ({
49
- ...row,
50
- organizationId,
51
- });
52
- const stamped = Array.isArray(data)
53
- ? data.map(stamp)
54
- : stamp(data as Record<string, unknown>);
55
- return originalValues.call(t, stamped);
56
- };
57
- }
58
- const v = Reflect.get(t, p, r);
59
- return typeof v === "function" ? v.bind(t) : v;
60
- },
61
- });
62
- };
63
- }
64
-
65
- return typeof value === "function" ? value.bind(target) : value;
66
- },
67
- }) as TDb;
68
- }
@@ -1,46 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import type { Actor } from "../../auth/types.js";
3
- import type { DatabaseAdapter } from "./adapter.js";
4
-
5
- export interface TxContext<TTx = unknown> {
6
- tx: TTx;
7
- actor: Actor | null;
8
- requestId: string;
9
- }
10
-
11
- export interface WithTransactionOptions {
12
- actor: Actor | null;
13
- requestId?: string;
14
- }
15
-
16
- export function createTxContext<TTx>(
17
- tx: TTx,
18
- options: WithTransactionOptions,
19
- ): TxContext<TTx> {
20
- return {
21
- tx,
22
- actor: options.actor,
23
- requestId: options.requestId ?? randomUUID(),
24
- };
25
- }
26
-
27
- export async function withTransaction<TDb, TTx, TResult>(
28
- database: DatabaseAdapter<TDb, TTx>,
29
- options: WithTransactionOptions,
30
- fn: (ctx: TxContext<TTx>) => Promise<TResult>,
31
- ): Promise<TResult> {
32
- return database.transaction(async (tx) => {
33
- return fn(createTxContext(tx, options));
34
- });
35
- }
36
-
37
- export function reuseOrCreateTxContext<TTx>(
38
- tx: TTx,
39
- options: WithTransactionOptions,
40
- existing?: TxContext<TTx> | null,
41
- ): TxContext<TTx> {
42
- if (existing) {
43
- return existing;
44
- }
45
- return createTxContext(tx, options);
46
- }
@@ -1,15 +0,0 @@
1
- import { toCommerceError } from "./errors.js";
2
- import type { ContentfulStatusCode } from "hono/utils/http-status";
3
-
4
- const statusByCode: Record<string, ContentfulStatusCode> = {
5
- NOT_FOUND: 404,
6
- VALIDATION_FAILED: 422,
7
- FORBIDDEN: 403,
8
- CONFLICT: 409,
9
- INVALID_TRANSITION: 422,
10
- };
11
-
12
- export function mapErrorToStatus(error: unknown): ContentfulStatusCode {
13
- const normalized = toCommerceError(error);
14
- return statusByCode[normalized.code] ?? 500;
15
- }
@@ -1,89 +0,0 @@
1
- export interface CommerceError {
2
- code: string;
3
- message: string;
4
- details?: unknown;
5
- }
6
-
7
- export interface FieldError {
8
- field: string;
9
- message: string;
10
- }
11
-
12
- export class CommerceNotFoundError extends Error implements CommerceError {
13
- code = "NOT_FOUND" as const;
14
- constructor(
15
- message: string,
16
- public details?: unknown,
17
- ) {
18
- super(message);
19
- this.name = "CommerceNotFoundError";
20
- }
21
- }
22
-
23
- export class CommerceValidationError extends Error implements CommerceError {
24
- code = "VALIDATION_FAILED" as const;
25
- constructor(
26
- message: string,
27
- public fieldErrors?: FieldError[],
28
- public details?: unknown,
29
- ) {
30
- super(message);
31
- this.name = "CommerceValidationError";
32
- }
33
- }
34
-
35
- export class CommerceForbiddenError extends Error implements CommerceError {
36
- code = "FORBIDDEN" as const;
37
- constructor(
38
- message: string,
39
- public details?: unknown,
40
- ) {
41
- super(message);
42
- this.name = "CommerceForbiddenError";
43
- }
44
- }
45
-
46
- export class CommerceConflictError extends Error implements CommerceError {
47
- code = "CONFLICT" as const;
48
- constructor(
49
- message: string,
50
- public details?: unknown,
51
- ) {
52
- super(message);
53
- this.name = "CommerceConflictError";
54
- }
55
- }
56
-
57
- export class CommerceInvalidTransitionError extends Error implements CommerceError {
58
- code = "INVALID_TRANSITION" as const;
59
- constructor(
60
- message: string,
61
- public details?: unknown,
62
- ) {
63
- super(message);
64
- this.name = "CommerceInvalidTransitionError";
65
- }
66
- }
67
-
68
- export function isCommerceError(value: unknown): value is CommerceError {
69
- if (!value || typeof value !== "object") return false;
70
- return "code" in value && "message" in value;
71
- }
72
-
73
- export function toCommerceError(error: unknown): CommerceError {
74
- if (isCommerceError(error)) {
75
- return error;
76
- }
77
- if (error instanceof Error) {
78
- return {
79
- code: "INTERNAL_ERROR",
80
- message: error.message,
81
- details: { name: error.name },
82
- };
83
- }
84
- return {
85
- code: "INTERNAL_ERROR",
86
- message: "Unexpected server error",
87
- details: error,
88
- };
89
- }
@@ -1,242 +0,0 @@
1
- import {
2
- eq,
3
- and,
4
- isNull,
5
- sql,
6
- getTableColumns,
7
- type SQL,
8
- type Column,
9
- type InferSelectModel,
10
- type InferInsertModel,
11
- } from "drizzle-orm";
12
- import type { PgTableWithColumns, TableConfig } from "drizzle-orm/pg-core";
13
- import { PgTable } from "drizzle-orm/pg-core";
14
- import type { TxContext } from "../database/tx-context.js";
15
- import type { DrizzleDatabase, DbOrTx } from "../database/drizzle-db.js";
16
- import { CommerceNotFoundError } from "../errors.js";
17
-
18
- /**
19
- * Filter type — partial record of column values to match with eq().
20
- * Only keys that exist on TRow and have non-undefined values are used.
21
- */
22
- export type Filters<TRow> = Partial<TRow>;
23
-
24
- /**
25
- * Options for findMany / findAndCount queries.
26
- */
27
- export interface FindOptions {
28
- limit?: number;
29
- offset?: number;
30
- orderBy?: Array<{ column: string; direction: "asc" | "desc" }>;
31
- withDeleted?: boolean;
32
- }
33
-
34
- /**
35
- * Standard CRUD operations derived from a Drizzle pgTable schema.
36
- * All methods support optional TxContext for transaction participation.
37
- */
38
- export interface BaseRepository<TRow, TInsert> {
39
- findById(id: string, ctx?: TxContext): Promise<TRow | undefined>;
40
- findMany(
41
- filters?: Filters<TRow>,
42
- options?: FindOptions,
43
- ctx?: TxContext,
44
- ): Promise<TRow[]>;
45
- findAndCount(
46
- filters?: Filters<TRow>,
47
- options?: FindOptions,
48
- ctx?: TxContext,
49
- ): Promise<{ rows: TRow[]; total: number }>;
50
- create(data: TInsert, ctx?: TxContext): Promise<TRow>;
51
- createMany(data: TInsert[], ctx?: TxContext): Promise<TRow[]>;
52
- update(id: string, data: Partial<TInsert>, ctx?: TxContext): Promise<TRow>;
53
- delete(id: string, ctx?: TxContext): Promise<void>;
54
- }
55
-
56
- /**
57
- * Extended repository for tables with a `deleted_at` column.
58
- * Adds softDelete and restore operations.
59
- */
60
- export interface SoftDeletableRepository<TRow, TInsert>
61
- extends BaseRepository<TRow, TInsert> {
62
- softDelete(id: string, ctx?: TxContext): Promise<void>;
63
- restore(id: string, ctx?: TxContext): Promise<TRow>;
64
- }
65
-
66
- /**
67
- * Type-level check: does the table config have a `deletedAt` or `deleted_at` column?
68
- */
69
- type HasDeletedAt<T extends PgTableWithColumns<TableConfig>> =
70
- "deletedAt" extends keyof T ? true : "deleted_at" extends keyof T ? true : false;
71
-
72
- /**
73
- * Conditional repository type: if the table has a deleted_at column,
74
- * return SoftDeletableRepository; otherwise BaseRepository.
75
- */
76
- export type RepositoryFor<T extends PgTableWithColumns<TableConfig>> =
77
- HasDeletedAt<T> extends true
78
- ? SoftDeletableRepository<InferSelectModel<T>, InferInsertModel<T>>
79
- : BaseRepository<InferSelectModel<T>, InferInsertModel<T>>;
80
-
81
- /**
82
- * Creates a typed repository with standard CRUD operations from a Drizzle table schema.
83
- *
84
- * Usage:
85
- * ```typescript
86
- * const repo = createRepository(schema.promotions, db)
87
- * const row = await repo.findById("abc-123")
88
- * const rows = await repo.findMany({ status: "active" }, { limit: 10 })
89
- * ```
90
- *
91
- * Tables with a `deletedAt` column automatically get `softDelete()` and `restore()`.
92
- * Domain-specific queries should remain in dedicated repository classes that
93
- * delegate standard CRUD to the factory-created instance.
94
- */
95
- export function createRepository<T extends PgTableWithColumns<TableConfig>>(
96
- table: T,
97
- db: DrizzleDatabase,
98
- ): RepositoryFor<T> {
99
- // Use getTableColumns for type-safe column access
100
- const columns = getTableColumns(table) as Record<string, Column>;
101
-
102
- // Runtime check for soft-delete column
103
- const hasSoftDelete = "deletedAt" in columns || "deleted_at" in columns;
104
- const deletedAtColumn: Column | null =
105
- columns["deletedAt"] ?? columns["deleted_at"] ?? null;
106
- const idColumn = columns["id"]!;
107
-
108
- // Drizzle's generic types require PgTable at the boundary.
109
- // We cast once here rather than at every call site.
110
- const pgTable = table as PgTable;
111
-
112
- function getDb(ctx?: TxContext): DbOrTx {
113
- return (ctx?.tx as DbOrTx | undefined) ?? db;
114
- }
115
-
116
- function buildWhereConditions(
117
- filters?: Filters<InferSelectModel<T>>,
118
- includeDeleted = false,
119
- ): SQL[] {
120
- const conditions: SQL[] = [];
121
-
122
- // Automatically exclude soft-deleted rows unless explicitly requested
123
- if (hasSoftDelete && !includeDeleted && deletedAtColumn) {
124
- conditions.push(isNull(deletedAtColumn));
125
- }
126
-
127
- if (filters) {
128
- for (const [key, value] of Object.entries(filters)) {
129
- const col = columns[key];
130
- if (value !== undefined && col) {
131
- conditions.push(eq(col, value));
132
- }
133
- }
134
- }
135
-
136
- return conditions;
137
- }
138
-
139
- const repo: BaseRepository<InferSelectModel<T>, InferInsertModel<T>> = {
140
- async findById(id, ctx) {
141
- const conditions = buildWhereConditions(undefined, false);
142
- conditions.push(eq(idColumn, id));
143
- const rows = await getDb(ctx)
144
- .select()
145
- .from(pgTable)
146
- .where(and(...conditions));
147
- return rows[0] as InferSelectModel<T> | undefined;
148
- },
149
-
150
- async findMany(filters, options = {}, ctx) {
151
- const conditions = buildWhereConditions(filters, options.withDeleted);
152
- let query = getDb(ctx).select().from(pgTable).$dynamic();
153
- if (conditions.length > 0) {
154
- query = query.where(and(...conditions));
155
- }
156
- if (options.limit !== undefined) {
157
- query = query.limit(options.limit);
158
- }
159
- if (options.offset !== undefined) {
160
- query = query.offset(options.offset);
161
- }
162
- return query as unknown as Promise<InferSelectModel<T>[]>;
163
- },
164
-
165
- async findAndCount(filters, options = {}, ctx) {
166
- const rows = await repo.findMany(filters, options, ctx);
167
- const conditions = buildWhereConditions(filters, options.withDeleted);
168
- let countQuery = getDb(ctx)
169
- .select({ count: sql<number>`count(*)::int` })
170
- .from(pgTable)
171
- .$dynamic();
172
- if (conditions.length > 0) {
173
- countQuery = countQuery.where(and(...conditions));
174
- }
175
- const countResult = await countQuery;
176
- return { rows, total: countResult[0]?.count ?? 0 };
177
- },
178
-
179
- async create(data, ctx) {
180
- const rows = await getDb(ctx)
181
- .insert(pgTable)
182
- .values(data as Record<string, unknown>)
183
- .returning();
184
- return rows[0] as InferSelectModel<T>;
185
- },
186
-
187
- async createMany(data, ctx) {
188
- if (data.length === 0) return [];
189
- const rows = await getDb(ctx)
190
- .insert(pgTable)
191
- .values(data as Record<string, unknown>[])
192
- .returning();
193
- return rows as InferSelectModel<T>[];
194
- },
195
-
196
- async update(id, data, ctx) {
197
- const rows = await getDb(ctx)
198
- .update(pgTable)
199
- .set(data as Record<string, unknown>)
200
- .where(eq(idColumn, id))
201
- .returning();
202
- if (!rows[0]) {
203
- throw new CommerceNotFoundError(`Record ${id} not found.`);
204
- }
205
- return rows[0] as InferSelectModel<T>;
206
- },
207
-
208
- async delete(id, ctx) {
209
- await getDb(ctx).delete(pgTable).where(eq(idColumn, id));
210
- },
211
- };
212
-
213
- if (hasSoftDelete && deletedAtColumn) {
214
- const softRepo = repo as SoftDeletableRepository<
215
- InferSelectModel<T>,
216
- InferInsertModel<T>
217
- >;
218
-
219
- softRepo.softDelete = async (id, ctx) => {
220
- await getDb(ctx)
221
- .update(pgTable)
222
- .set({ deletedAt: new Date() } as Record<string, unknown>)
223
- .where(eq(idColumn, id));
224
- };
225
-
226
- softRepo.restore = async (id, ctx) => {
227
- const rows = await getDb(ctx)
228
- .update(pgTable)
229
- .set({ deletedAt: null } as Record<string, unknown>)
230
- .where(eq(idColumn, id))
231
- .returning();
232
- if (!rows[0]) {
233
- throw new CommerceNotFoundError(`Record ${id} not found.`);
234
- }
235
- return rows[0] as InferSelectModel<T>;
236
- };
237
-
238
- return softRepo as RepositoryFor<T>;
239
- }
240
-
241
- return repo as RepositoryFor<T>;
242
- }
@@ -1,43 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import type { Actor } from "../../auth/types.js";
3
- import type { JobsAdapter } from "../jobs/adapter.js";
4
- import { NullJobsAdapter } from "../jobs/adapter.js";
5
- import type { PluginDb } from "../database/plugin-types.js";
6
- import type { HookContext, HookOrigin, Logger, ServiceContainer } from "./types.js";
7
-
8
- export interface CreateHookContextArgs {
9
- actor: Actor | null;
10
- tx?: unknown;
11
- logger: Logger;
12
- services: ServiceContainer;
13
- context?: Record<string, unknown>;
14
- requestId?: string;
15
- origin?: HookOrigin;
16
- jobs?: JobsAdapter;
17
- db?: PluginDb;
18
- kernel?: { database: { db: PluginDb } };
19
- }
20
-
21
- const nullJobs = new NullJobsAdapter();
22
-
23
- /**
24
- * Creates a HookContext with sensible defaults.
25
- */
26
- export function createHookContext(args: CreateHookContextArgs): HookContext {
27
- // Resolve db: prefer explicit db arg, fall back to kernel.database.db
28
- const db = args.db ?? args.kernel?.database?.db ?? null;
29
-
30
- const ctx: HookContext = {
31
- actor: args.actor,
32
- tx: args.tx ?? null,
33
- logger: args.logger,
34
- services: args.services,
35
- context: args.context ?? {},
36
- requestId: args.requestId ?? randomUUID(),
37
- origin: args.origin ?? "rest",
38
- jobs: args.jobs ?? nullJobs,
39
- db: db as PluginDb,
40
- };
41
-
42
- return ctx;
43
- }
@@ -1,88 +0,0 @@
1
- import type { AfterHook, BeforeHook, HookContext, HookOperation } from "./types.js";
2
-
3
- export interface HookError {
4
- hookName: string;
5
- message: string;
6
- }
7
-
8
- export interface HookReport {
9
- errors: HookError[];
10
- hasErrors: boolean;
11
- }
12
-
13
- /** Default hook timeout: 20 seconds */
14
- const HOOK_TIMEOUT_MS = 20_000;
15
-
16
- function withTimeout<T>(promiseOrValue: Promise<T> | T, timeoutMs: number, hookName: string): Promise<T> {
17
- const promise = Promise.resolve(promiseOrValue);
18
- return new Promise<T>((resolve, reject) => {
19
- const timer = setTimeout(
20
- () => reject(new Error(`Hook "${hookName}" timed out after ${timeoutMs}ms`)),
21
- timeoutMs,
22
- );
23
- promise.then(
24
- (val) => { clearTimeout(timer); resolve(val); },
25
- (err) => { clearTimeout(timer); reject(err); },
26
- );
27
- });
28
- }
29
-
30
- export async function runBeforeHooks<T>(
31
- hooks: BeforeHook<T>[],
32
- data: T,
33
- operation: HookOperation,
34
- context: HookContext,
35
- ): Promise<T> {
36
- let current = data;
37
- for (const hook of hooks) {
38
- const hookName = hook.name || "(anonymous beforeHook)";
39
- try {
40
- current = await withTimeout(
41
- hook({ data: current, operation, context }),
42
- HOOK_TIMEOUT_MS,
43
- hookName,
44
- );
45
- } catch (error) {
46
- context.logger.error(`Before-hook "${hookName}" failed during ${operation}`, {
47
- error: error instanceof Error ? error.message : String(error),
48
- requestId: context.requestId,
49
- });
50
- throw error; // Re-throw — beforeHooks MUST succeed
51
- }
52
- }
53
- return current;
54
- }
55
-
56
- export async function runAfterHooks<T>(
57
- hooks: AfterHook<T>[],
58
- originalData: T | null,
59
- committedResult: T,
60
- operation: HookOperation,
61
- context: HookContext,
62
- ): Promise<HookReport> {
63
- const errors: HookError[] = [];
64
- for (const hook of hooks) {
65
- const hookName = hook.name || "(anonymous afterHook)";
66
- try {
67
- await withTimeout(
68
- hook({
69
- data: originalData,
70
- result: committedResult,
71
- operation,
72
- context,
73
- }),
74
- HOOK_TIMEOUT_MS,
75
- hookName,
76
- );
77
- } catch (error) {
78
- errors.push({
79
- hookName,
80
- message: error instanceof Error ? error.message : String(error),
81
- });
82
- context.logger.error(`After-hook "${hookName}" failed`, {
83
- error,
84
- });
85
- }
86
- }
87
- return { errors, hasErrors: errors.length > 0 };
88
- }