@unifiedcommerce/core 0.0.4 → 0.1.1

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 (179) hide show
  1. package/dist/auth/auth-schema.d.ts +92 -0
  2. package/dist/auth/auth-schema.d.ts.map +1 -1
  3. package/dist/auth/auth-schema.js +7 -0
  4. package/dist/auth/setup.d.ts.map +1 -1
  5. package/dist/auth/setup.js +3 -1
  6. package/package.json +1 -2
  7. package/src/adapters/console-email.ts +0 -43
  8. package/src/auth/access.ts +0 -187
  9. package/src/auth/auth-schema.ts +0 -131
  10. package/src/auth/middleware.ts +0 -161
  11. package/src/auth/org.ts +0 -41
  12. package/src/auth/permissions.ts +0 -28
  13. package/src/auth/setup.ts +0 -165
  14. package/src/auth/system-actor.ts +0 -19
  15. package/src/auth/types.ts +0 -10
  16. package/src/config/defaults.ts +0 -82
  17. package/src/config/define-config.ts +0 -53
  18. package/src/config/types.ts +0 -299
  19. package/src/generated/plugin-capabilities.d.ts +0 -20
  20. package/src/generated/plugin-manifest.ts +0 -23
  21. package/src/generated/plugin-repositories.d.ts +0 -20
  22. package/src/hooks/checkout-completion.ts +0 -262
  23. package/src/hooks/checkout.ts +0 -677
  24. package/src/hooks/order-emails.ts +0 -62
  25. package/src/index.ts +0 -214
  26. package/src/interfaces/mcp/agent-prompt.ts +0 -174
  27. package/src/interfaces/mcp/context-enrichment.ts +0 -177
  28. package/src/interfaces/mcp/server.ts +0 -617
  29. package/src/interfaces/mcp/transport.ts +0 -68
  30. package/src/interfaces/rest/customer-portal.ts +0 -299
  31. package/src/interfaces/rest/index.ts +0 -74
  32. package/src/interfaces/rest/router.ts +0 -334
  33. package/src/interfaces/rest/routes/admin-jobs.ts +0 -58
  34. package/src/interfaces/rest/routes/audit.ts +0 -50
  35. package/src/interfaces/rest/routes/carts.ts +0 -89
  36. package/src/interfaces/rest/routes/catalog.ts +0 -493
  37. package/src/interfaces/rest/routes/checkout.ts +0 -283
  38. package/src/interfaces/rest/routes/inventory.ts +0 -70
  39. package/src/interfaces/rest/routes/media.ts +0 -86
  40. package/src/interfaces/rest/routes/orders.ts +0 -78
  41. package/src/interfaces/rest/routes/payments.ts +0 -60
  42. package/src/interfaces/rest/routes/pricing.ts +0 -57
  43. package/src/interfaces/rest/routes/promotions.ts +0 -92
  44. package/src/interfaces/rest/routes/search.ts +0 -71
  45. package/src/interfaces/rest/routes/webhooks.ts +0 -46
  46. package/src/interfaces/rest/schemas/admin-jobs.ts +0 -40
  47. package/src/interfaces/rest/schemas/audit.ts +0 -46
  48. package/src/interfaces/rest/schemas/carts.ts +0 -125
  49. package/src/interfaces/rest/schemas/catalog.ts +0 -450
  50. package/src/interfaces/rest/schemas/checkout.ts +0 -66
  51. package/src/interfaces/rest/schemas/customer-portal.ts +0 -195
  52. package/src/interfaces/rest/schemas/inventory.ts +0 -138
  53. package/src/interfaces/rest/schemas/media.ts +0 -75
  54. package/src/interfaces/rest/schemas/orders.ts +0 -104
  55. package/src/interfaces/rest/schemas/pricing.ts +0 -80
  56. package/src/interfaces/rest/schemas/promotions.ts +0 -110
  57. package/src/interfaces/rest/schemas/responses.ts +0 -85
  58. package/src/interfaces/rest/schemas/search.ts +0 -58
  59. package/src/interfaces/rest/schemas/shared.ts +0 -62
  60. package/src/interfaces/rest/schemas/webhooks.ts +0 -68
  61. package/src/interfaces/rest/utils.ts +0 -104
  62. package/src/interfaces/rest/webhook-router.ts +0 -50
  63. package/src/kernel/compensation/executor.ts +0 -61
  64. package/src/kernel/compensation/types.ts +0 -26
  65. package/src/kernel/database/adapter.ts +0 -13
  66. package/src/kernel/database/drizzle-db.ts +0 -56
  67. package/src/kernel/database/migrate.ts +0 -76
  68. package/src/kernel/database/plugin-types.ts +0 -34
  69. package/src/kernel/database/schema.ts +0 -49
  70. package/src/kernel/database/scoped-db.ts +0 -68
  71. package/src/kernel/database/tx-context.ts +0 -46
  72. package/src/kernel/error-mapper.ts +0 -15
  73. package/src/kernel/errors.ts +0 -89
  74. package/src/kernel/factory/repository-factory.ts +0 -242
  75. package/src/kernel/hooks/create-context.ts +0 -43
  76. package/src/kernel/hooks/executor.ts +0 -88
  77. package/src/kernel/hooks/registry.ts +0 -74
  78. package/src/kernel/hooks/types.ts +0 -52
  79. package/src/kernel/http-error.ts +0 -44
  80. package/src/kernel/jobs/adapter.ts +0 -36
  81. package/src/kernel/jobs/drizzle-adapter.ts +0 -58
  82. package/src/kernel/jobs/runner.ts +0 -153
  83. package/src/kernel/jobs/schema.ts +0 -46
  84. package/src/kernel/jobs/types.ts +0 -30
  85. package/src/kernel/local-api.ts +0 -185
  86. package/src/kernel/plugin/manifest.ts +0 -253
  87. package/src/kernel/query/executor.ts +0 -184
  88. package/src/kernel/query/registry.ts +0 -46
  89. package/src/kernel/result.ts +0 -33
  90. package/src/kernel/schema/extra-columns.ts +0 -37
  91. package/src/kernel/service-registry.ts +0 -76
  92. package/src/kernel/service-timing.ts +0 -89
  93. package/src/kernel/state-machine/machine.ts +0 -101
  94. package/src/modules/analytics/drizzle-adapter.ts +0 -426
  95. package/src/modules/analytics/hooks.ts +0 -11
  96. package/src/modules/analytics/models.ts +0 -125
  97. package/src/modules/analytics/repository/index.ts +0 -6
  98. package/src/modules/analytics/service.ts +0 -245
  99. package/src/modules/analytics/types.ts +0 -180
  100. package/src/modules/audit/hooks.ts +0 -78
  101. package/src/modules/audit/schema.ts +0 -33
  102. package/src/modules/audit/service.ts +0 -151
  103. package/src/modules/cart/access.ts +0 -27
  104. package/src/modules/cart/matcher.ts +0 -26
  105. package/src/modules/cart/repository/index.ts +0 -234
  106. package/src/modules/cart/schema.ts +0 -42
  107. package/src/modules/cart/schemas.ts +0 -38
  108. package/src/modules/cart/service.ts +0 -541
  109. package/src/modules/catalog/repository/index.ts +0 -772
  110. package/src/modules/catalog/schema.ts +0 -203
  111. package/src/modules/catalog/schemas.ts +0 -104
  112. package/src/modules/catalog/service.ts +0 -1544
  113. package/src/modules/customers/repository/index.ts +0 -327
  114. package/src/modules/customers/schema.ts +0 -64
  115. package/src/modules/customers/service.ts +0 -171
  116. package/src/modules/fulfillment/repository/index.ts +0 -426
  117. package/src/modules/fulfillment/schema.ts +0 -101
  118. package/src/modules/fulfillment/service.ts +0 -555
  119. package/src/modules/fulfillment/types.ts +0 -59
  120. package/src/modules/inventory/repository/index.ts +0 -509
  121. package/src/modules/inventory/schema.ts +0 -94
  122. package/src/modules/inventory/schemas.ts +0 -38
  123. package/src/modules/inventory/service.ts +0 -490
  124. package/src/modules/media/adapter.ts +0 -17
  125. package/src/modules/media/repository/index.ts +0 -274
  126. package/src/modules/media/schema.ts +0 -41
  127. package/src/modules/media/service.ts +0 -151
  128. package/src/modules/orders/repository/index.ts +0 -287
  129. package/src/modules/orders/schema.ts +0 -66
  130. package/src/modules/orders/service.ts +0 -619
  131. package/src/modules/orders/stale-order-cleanup.ts +0 -76
  132. package/src/modules/organization/service.ts +0 -191
  133. package/src/modules/payments/adapter.ts +0 -47
  134. package/src/modules/payments/repository/index.ts +0 -6
  135. package/src/modules/payments/service.ts +0 -107
  136. package/src/modules/pricing/repository/index.ts +0 -291
  137. package/src/modules/pricing/schema.ts +0 -71
  138. package/src/modules/pricing/schemas.ts +0 -38
  139. package/src/modules/pricing/service.ts +0 -494
  140. package/src/modules/promotions/repository/index.ts +0 -325
  141. package/src/modules/promotions/schema.ts +0 -62
  142. package/src/modules/promotions/schemas.ts +0 -38
  143. package/src/modules/promotions/service.ts +0 -598
  144. package/src/modules/search/adapter.ts +0 -57
  145. package/src/modules/search/hooks.ts +0 -12
  146. package/src/modules/search/repository/index.ts +0 -6
  147. package/src/modules/search/service.ts +0 -315
  148. package/src/modules/shipping/calculator.ts +0 -188
  149. package/src/modules/shipping/repository/index.ts +0 -6
  150. package/src/modules/shipping/service.ts +0 -51
  151. package/src/modules/tax/adapter.ts +0 -60
  152. package/src/modules/tax/repository/index.ts +0 -6
  153. package/src/modules/tax/service.ts +0 -53
  154. package/src/modules/webhooks/hook.ts +0 -34
  155. package/src/modules/webhooks/repository/index.ts +0 -278
  156. package/src/modules/webhooks/schema.ts +0 -56
  157. package/src/modules/webhooks/service.ts +0 -117
  158. package/src/modules/webhooks/signing.ts +0 -6
  159. package/src/modules/webhooks/ssrf-guard.ts +0 -71
  160. package/src/modules/webhooks/tasks.ts +0 -52
  161. package/src/modules/webhooks/worker.ts +0 -134
  162. package/src/runtime/commerce.ts +0 -145
  163. package/src/runtime/kernel.ts +0 -419
  164. package/src/runtime/logger.ts +0 -36
  165. package/src/runtime/server.ts +0 -349
  166. package/src/runtime/shutdown.ts +0 -43
  167. package/src/test-utils/create-pglite-adapter.ts +0 -129
  168. package/src/test-utils/create-plugin-test-app.ts +0 -128
  169. package/src/test-utils/create-repository-test-harness.ts +0 -16
  170. package/src/test-utils/create-test-config.ts +0 -190
  171. package/src/test-utils/create-test-kernel.ts +0 -7
  172. package/src/test-utils/create-test-plugin-context.ts +0 -75
  173. package/src/test-utils/rest-api-test-utils.ts +0 -265
  174. package/src/test-utils/test-actors.ts +0 -62
  175. package/src/test-utils/typed-hooks.ts +0 -54
  176. package/src/types/commerce-types.ts +0 -34
  177. package/src/utils/id.ts +0 -3
  178. package/src/utils/logger.ts +0 -18
  179. package/src/utils/pagination.ts +0 -22
@@ -1,85 +0,0 @@
1
- /**
2
- * Typed response schemas derived from Drizzle table definitions.
3
- *
4
- * Uses drizzle-zod's createSelectSchema() to generate Zod schemas that
5
- * match the exact database column types. These replace z.any() in route
6
- * response definitions, making the OpenAPI spec show real field names
7
- * and types instead of empty {}.
8
- */
9
-
10
- import { createSelectSchema } from "drizzle-zod";
11
- import { z } from "@hono/zod-openapi";
12
- import { orders, orderLineItems } from "../../../modules/orders/schema.js";
13
- import { carts, cartLineItems } from "../../../modules/cart/schema.js";
14
- import { customers, customerAddresses } from "../../../modules/customers/schema.js";
15
- import { sellableEntities } from "../../../modules/catalog/schema.js";
16
- import { commerceJobs } from "../../../kernel/jobs/schema.js";
17
-
18
- // ─── Orders ──────────────────────────────────────────────────────────────────
19
-
20
- export const OrderSchema = createSelectSchema(orders).openapi("Order");
21
-
22
- export const OrderLineItemSchema = createSelectSchema(orderLineItems).openapi("OrderLineItem");
23
-
24
- export const OrderWithItemsSchema = z.object({
25
- ...OrderSchema.shape,
26
- lineItems: z.array(OrderLineItemSchema).optional(),
27
- }).openapi("OrderWithItems");
28
-
29
- // ─── Carts ───────────────────────────────────────────────────────────────────
30
-
31
- export const CartSchema = createSelectSchema(carts).openapi("Cart");
32
-
33
- export const CartLineItemSchema = createSelectSchema(cartLineItems).openapi("CartLineItem");
34
-
35
- export const CartWithItemsSchema = z.object({
36
- ...CartSchema.shape,
37
- lineItems: z.array(CartLineItemSchema).optional(),
38
- }).openapi("CartWithItems");
39
-
40
- // ─── Customers ───────────────────────────────────────────────────────────────
41
-
42
- export const CustomerSchema = createSelectSchema(customers).openapi("Customer");
43
-
44
- export const CustomerAddressSchema = createSelectSchema(customerAddresses).openapi("CustomerAddress");
45
-
46
- // ─── Catalog ─────────────────────────────────────────────────────────────────
47
-
48
- export const CatalogEntitySchema = createSelectSchema(sellableEntities).openapi("CatalogEntity");
49
-
50
- // ─── Jobs ────────────────────────────────────────────────────────────────────
51
-
52
- export const JobSchema = createSelectSchema(commerceJobs).openapi("Job");
53
-
54
- // ─── Wrapped Response Helpers ────────────────────────────────────────────────
55
- // These wrap a schema in { data: T } for consistent API response format.
56
-
57
- export function dataResponse<T extends z.ZodType>(schema: T, name: string) {
58
- return z.object({ data: schema }).openapi(name);
59
- }
60
-
61
- export function dataArrayResponse<T extends z.ZodType>(schema: T, name: string) {
62
- return z.object({ data: z.array(schema) }).openapi(name);
63
- }
64
-
65
- export function paginatedResponse<T extends z.ZodType>(schema: T, name: string) {
66
- return z.object({
67
- data: z.array(schema),
68
- meta: z.object({
69
- page: z.number(),
70
- limit: z.number(),
71
- total: z.number().optional(),
72
- }).optional(),
73
- }).openapi(name);
74
- }
75
-
76
- // ─── Pre-built Response Schemas ──────────────────────────────────────────────
77
-
78
- export const OrderResponse = dataResponse(OrderSchema, "OrderResponse");
79
- export const OrderListResponse = paginatedResponse(OrderSchema, "OrderListResponse");
80
- export const CartResponse = dataResponse(CartWithItemsSchema, "CartResponse");
81
- export const CustomerResponse = dataResponse(CustomerSchema, "CustomerResponse");
82
- export const CustomerAddressListResponse = dataArrayResponse(CustomerAddressSchema, "CustomerAddressListResponse");
83
- export const CatalogEntityResponse = dataResponse(CatalogEntitySchema, "CatalogEntityResponse");
84
- export const CatalogEntityListResponse = paginatedResponse(CatalogEntitySchema, "CatalogEntityListResponse");
85
- export const JobListResponse = dataArrayResponse(JobSchema, "JobListResponse");
@@ -1,58 +0,0 @@
1
- import { z, createRoute } from "@hono/zod-openapi";
2
- import { CatalogEntitySchema } from "./responses.js";
3
-
4
- // ─── Route Definitions ──────────────────────────────────────────────────────
5
-
6
- export const searchRoute = createRoute({
7
- method: "get",
8
- path: "/",
9
- tags: ["Search"],
10
- summary: "Search catalog entities",
11
- request: {
12
- query: z.object({
13
- q: z.string().optional(),
14
- type: z.string().optional(),
15
- category: z.string().optional(),
16
- brand: z.string().optional(),
17
- status: z.string().optional(),
18
- page: z.string().optional(),
19
- limit: z.string().optional(),
20
- facets: z.string().optional(),
21
- }),
22
- },
23
- responses: {
24
- 200: {
25
- content: { "application/json": { schema: z.object({
26
- data: z.array(CatalogEntitySchema),
27
- meta: z.object({
28
- pagination: z.object({
29
- page: z.number(),
30
- limit: z.number(),
31
- total: z.number().optional(),
32
- }),
33
- }).optional(),
34
- }) } },
35
- description: "Search results",
36
- },
37
- },
38
- });
39
-
40
- export const suggestRoute = createRoute({
41
- method: "get",
42
- path: "/suggest",
43
- tags: ["Search"],
44
- summary: "Get search suggestions",
45
- request: {
46
- query: z.object({
47
- prefix: z.string().optional(),
48
- type: z.string().optional(),
49
- limit: z.string().optional(),
50
- }),
51
- },
52
- responses: {
53
- 200: {
54
- content: { "application/json": { schema: z.object({ data: z.array(z.object({ id: z.string(), slug: z.string(), title: z.string().optional() })) }) } },
55
- description: "Suggestions",
56
- },
57
- },
58
- });
@@ -1,62 +0,0 @@
1
- import { z } from "@hono/zod-openapi";
2
-
3
- // ─── Error Response ──────────────────────────────────────────────────────────
4
-
5
- export const ErrorSchema = z.object({
6
- error: z.object({
7
- code: z.string().openapi({ example: "VALIDATION_FAILED" }),
8
- message: z.string().openapi({ example: "cartId: Invalid uuid" }),
9
- }),
10
- }).openapi("Error");
11
-
12
- // ─── Pagination ──────────────────────────────────────────────────────────────
13
-
14
- export const PaginationQuerySchema = z.object({
15
- page: z.string().optional().openapi({ example: "1" }),
16
- limit: z.string().optional().openapi({ example: "20" }),
17
- });
18
-
19
- export const PaginationMetaSchema = z.object({
20
- page: z.number(),
21
- limit: z.number(),
22
- total: z.number().optional(),
23
- }).openapi("PaginationMeta");
24
-
25
- // ─── Common Params ───────────────────────────────────────────────────────────
26
-
27
- export const UuidParamSchema = z.object({
28
- id: z.uuid().openapi({ example: "550e8400-e29b-41d4-a716-446655440000" }),
29
- });
30
-
31
- export const IdOrSlugParamSchema = z.object({
32
- idOrSlug: z.string().min(1).openapi({ example: "my-product" }),
33
- });
34
-
35
- // ─── Common Responses ────────────────────────────────────────────────────────
36
-
37
- export const DeletedResponseSchema = z.object({
38
- data: z.object({ deleted: z.literal(true) }),
39
- }).openapi("DeletedResponse");
40
-
41
- export const errorResponses = {
42
- 400: {
43
- content: { "application/json": { schema: ErrorSchema } },
44
- description: "Business logic error.",
45
- },
46
- 401: {
47
- content: { "application/json": { schema: ErrorSchema } },
48
- description: "Authentication required.",
49
- },
50
- 403: {
51
- content: { "application/json": { schema: ErrorSchema } },
52
- description: "Insufficient permissions.",
53
- },
54
- 404: {
55
- content: { "application/json": { schema: ErrorSchema } },
56
- description: "Resource not found.",
57
- },
58
- 422: {
59
- content: { "application/json": { schema: ErrorSchema } },
60
- description: "Validation error.",
61
- },
62
- } as const;
@@ -1,68 +0,0 @@
1
- import { z, createRoute } from "@hono/zod-openapi";
2
- import { ErrorSchema, errorResponses } from "./shared.js";
3
-
4
- // ─── Request Schema ──────────────────────────────────────────────────────────
5
-
6
- export const CreateWebhookEndpointBodySchema = z.object({
7
- url: z.string().url().openapi({ example: "https://example.com/webhooks" }),
8
- events: z.array(z.string()).openapi({ example: ["order.created", "order.fulfilled"] }),
9
- secret: z.string().optional().openapi({ example: "whsec_abc123" }),
10
- metadata: z.record(z.string(), z.unknown()).optional(),
11
- }).openapi("CreateWebhookEndpointRequest");
12
-
13
- // ─── Route Definitions ──────────────────────────────────────────────────────
14
-
15
- export const listWebhookEndpointsRoute = createRoute({
16
- method: "get",
17
- path: "/",
18
- tags: ["Webhooks"],
19
- summary: "List webhook endpoints",
20
- responses: {
21
- 200: {
22
- content: { "application/json": { schema: z.object({ data: z.array(z.record(z.string(), z.unknown())) }) } },
23
- description: "Webhook endpoints",
24
- },
25
- },
26
- });
27
-
28
- export const deleteWebhookEndpointRoute = createRoute({
29
- method: "delete",
30
- path: "/{id}",
31
- tags: ["Webhooks"],
32
- summary: "Delete a webhook endpoint",
33
- request: {
34
- params: z.object({
35
- id: z.uuid().openapi({ example: "550e8400-e29b-41d4-a716-446655440000" }),
36
- }),
37
- },
38
- responses: {
39
- 200: {
40
- content: { "application/json": { schema: z.object({ data: z.object({ deleted: z.literal(true) }) }) } },
41
- description: "Webhook endpoint deleted.",
42
- },
43
- ...errorResponses,
44
- },
45
- });
46
-
47
- export const createWebhookEndpointRoute = createRoute({
48
- method: "post",
49
- path: "/",
50
- tags: ["Webhooks"],
51
- summary: "Create a webhook endpoint",
52
- description: "Registers a new webhook endpoint that will receive event notifications.",
53
- request: {
54
- body: {
55
- content: {
56
- "application/json": { schema: CreateWebhookEndpointBodySchema },
57
- },
58
- required: true,
59
- },
60
- },
61
- responses: {
62
- 201: {
63
- content: { "application/json": { schema: z.object({ data: z.record(z.string(), z.unknown()) }) } },
64
- description: "Webhook endpoint created.",
65
- },
66
- ...errorResponses,
67
- },
68
- });
@@ -1,104 +0,0 @@
1
- import type { CommerceError } from "../../kernel/errors.js";
2
- import { mapErrorToStatus } from "../../kernel/error-mapper.js";
3
- import { toCommerceError } from "../../kernel/errors.js";
4
- import type { Actor } from "../../auth/types.js";
5
-
6
- /**
7
- * Shared Hono environment type for all sub-routers.
8
- * Matches the Variables set by middleware in the top-level server app.
9
- */
10
- export type AppEnv = {
11
- Variables: {
12
- actor: Actor | null;
13
- requestId: string;
14
- logger: unknown;
15
- kernel: unknown;
16
- };
17
- };
18
-
19
- const MAX_PAGE_LIMIT = 100;
20
-
21
- export function parsePagination(query: Record<string, string | undefined>): {
22
- page: number;
23
- limit: number;
24
- } {
25
- const page = Number.parseInt(query.page ?? "1", 10);
26
- const limit = Number.parseInt(query.limit ?? "20", 10);
27
- return {
28
- page: Number.isFinite(page) && page > 0 ? page : 1,
29
- limit: Math.min(MAX_PAGE_LIMIT, Number.isFinite(limit) && limit > 0 ? limit : 20),
30
- };
31
- }
32
-
33
- export function parseInclude(value?: string): Set<string> {
34
- if (!value) return new Set();
35
- return new Set(
36
- value
37
- .split(",")
38
- .map((item) => item.trim())
39
- .filter(Boolean),
40
- );
41
- }
42
-
43
- /**
44
- * Map an error to a safe client response. Internal errors are sanitized
45
- * to prevent leaking SQL, schema, or stack trace details.
46
- */
47
- export function mapErrorToResponse(error: unknown): { error: { code: string; message: string } } {
48
- const ce = toCommerceError(error);
49
- if (ce.code === "INTERNAL_ERROR") {
50
- // Sanitize internal errors -- do not expose raw messages to clients
51
- return { error: { code: "INTERNAL_ERROR", message: "An unexpected error occurred." } };
52
- }
53
- return { error: { code: ce.code, message: ce.message } };
54
- }
55
-
56
- export { mapErrorToStatus };
57
-
58
- /**
59
- * Hono middleware that requires a specific permission on the actor.
60
- * Returns 401 if no actor, 403 if permission denied.
61
- * Usage: router.post("/", requirePerm("webhooks:manage"), handler);
62
- */
63
- export function requirePerm(permission: string) {
64
- return async (c: { get(key: string): unknown; json(data: unknown, status: number): unknown }, next: () => Promise<void>) => {
65
- const actor = c.get("actor") as { permissions?: string[] } | null;
66
- if (!actor) {
67
- return c.json({ error: { code: "UNAUTHORIZED", message: "Authentication required." } }, 401);
68
- }
69
- const perms = actor.permissions ?? [];
70
- if (perms.includes(permission) || perms.includes("*:*")) {
71
- await next();
72
- return;
73
- }
74
- // Check resource-level wildcard (e.g., "catalog:*" matches "catalog:create")
75
- const [resource] = permission.split(":");
76
- if (resource && perms.includes(`${resource}:*`)) {
77
- await next();
78
- return;
79
- }
80
- return c.json({ error: { code: "FORBIDDEN", message: `Permission '${permission}' is required.` } }, 403);
81
- };
82
- }
83
-
84
- export function parseSort(
85
- value?: string,
86
- ):
87
- | {
88
- field: "createdAt" | "updatedAt" | "slug";
89
- direction: "asc" | "desc";
90
- }
91
- | undefined {
92
- if (!value) return undefined;
93
- const [fieldRaw, directionRaw] = value.split(":");
94
- const selectedField = fieldRaw ?? "createdAt";
95
- const field = ["createdAt", "updatedAt", "slug"].includes(selectedField)
96
- ? (selectedField as "createdAt" | "updatedAt" | "slug")
97
- : "createdAt";
98
- const direction = directionRaw === "asc" ? "asc" : "desc";
99
- return { field, direction };
100
- }
101
-
102
- export function isUUID(value: string): boolean {
103
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
104
- }
@@ -1,50 +0,0 @@
1
- /**
2
- * webhookRouter() — route builder for external webhook receivers.
3
- *
4
- * Unlike router() which uses UC authentication (.auth(), .permission()),
5
- * webhookRouter() uses HMAC signature verification. This is for routes
6
- * that receive callbacks from external services (Shopify, WooCommerce,
7
- * Stripe, BNPL providers) that authenticate via provider-specific signatures.
8
- *
9
- * Provides typed access to kernel.services, kernel.database.db, and
10
- * kernel.logger without casting through `unknown`.
11
- *
12
- * Usage:
13
- *
14
- * import { webhookRouter } from "@unifiedcommerce/core";
15
- *
16
- * export function createMyWebhookRoutes(kernel: Kernel) {
17
- * const { app, services, db, logger } = webhookRouter(kernel);
18
- *
19
- * app.post("/product-updated", async (c) => {
20
- * const body = await c.req.json();
21
- * await services.catalog.update(body.id, { ... }, systemActor);
22
- * return c.json({ status: "ok" });
23
- * });
24
- *
25
- * return app;
26
- * }
27
- */
28
-
29
- import { Hono } from "hono";
30
- import type { Kernel } from "../../runtime/kernel.js";
31
-
32
- export interface WebhookRouterResult {
33
- /** Raw Hono app — mount routes on this. No UC auth middleware attached. */
34
- app: Hono;
35
- /** Kernel services (catalog, inventory, orders, etc.). Typed properly. */
36
- services: Kernel["services"];
37
- /** Drizzle database instance for direct queries. */
38
- db: Kernel["database"]["db"];
39
- /** Structured Pino logger. */
40
- logger: Kernel["logger"];
41
- }
42
-
43
- export function webhookRouter(kernel: Kernel): WebhookRouterResult {
44
- return {
45
- app: new Hono(),
46
- services: kernel.services,
47
- db: kernel.database.db,
48
- logger: kernel.logger,
49
- };
50
- }
@@ -1,61 +0,0 @@
1
- import type { CompensationContext, Step } from "./types.js";
2
- import type { Result } from "../result.js";
3
-
4
- /**
5
- * AnyStep erases the output type parameter so that heterogeneous step
6
- * arrays can be passed to runCompensationChain without variance issues
7
- * under exactOptionalPropertyTypes.
8
- */
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- erases output type for heterogeneous step arrays; unknown breaks contravariance on compensate()
10
- type AnyStep<TInput> = Step<TInput, any>;
11
-
12
- /**
13
- * Runs a list of steps in order. If any step fails, compensates all
14
- * previously completed steps in reverse. Steps share the same input
15
- * object (they may mutate it to enrich downstream steps, following
16
- * the same pattern established by BeforeHooks).
17
- *
18
- * Compensation failures are logged but do not override the original error.
19
- * A failed compensation is a separate operational concern that requires
20
- * manual review — it should never mask the root cause returned to the caller.
21
- */
22
- export async function runCompensationChain<TInput>(
23
- steps: ReadonlyArray<AnyStep<TInput>>,
24
- input: TInput,
25
- ctx: CompensationContext,
26
- ): Promise<Result<TInput>> {
27
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- matches AnyStep output erasure
28
- const completed: Array<{ step: AnyStep<TInput>; output: any }> = [];
29
-
30
- for (const step of steps) {
31
- const result = await step.run(input, ctx);
32
-
33
- if (!result.ok) {
34
- ctx.hook.logger.error(
35
- `Compensation chain failed at step "${step.id}". ` +
36
- `Running ${completed.length} compensation(s).`,
37
- { error: result.error },
38
- );
39
-
40
- // Compensate in reverse order — most recently completed step first
41
- for (const done of [...completed].reverse()) {
42
- if (!done.step.compensate) continue;
43
- try {
44
- await done.step.compensate(done.output, ctx);
45
- ctx.hook.logger.info(`Compensated step "${done.step.id}"`);
46
- } catch (compensateError) {
47
- ctx.hook.logger.error(
48
- `Compensation for step "${done.step.id}" failed. Manual review required.`,
49
- { compensateError },
50
- );
51
- }
52
- }
53
-
54
- return result;
55
- }
56
-
57
- completed.push({ step, output: result.value });
58
- }
59
-
60
- return { ok: true, value: input };
61
- }
@@ -1,26 +0,0 @@
1
- import type { TxContext } from "../database/tx-context.js";
2
- import type { HookContext } from "../hooks/types.js";
3
- import type { Result } from "../result.js";
4
-
5
- /**
6
- * CompensationContext carries the transaction and hook context into
7
- * both the run and compensate functions. Steps have access to services,
8
- * the actor, and the logger through ctx.hook.
9
- */
10
- export interface CompensationContext {
11
- tx: TxContext | null;
12
- hook: HookContext;
13
- }
14
-
15
- /**
16
- * A Step is one unit of work in a compensation chain.
17
- *
18
- * TInput is the data the step receives (typically the shared checkout data object).
19
- * TOutput is what the step produces. This same value is passed to compensate()
20
- * so the compensate function has everything it needs to reverse the work.
21
- */
22
- export interface Step<TInput, TOutput> {
23
- id: string;
24
- run: (input: TInput, ctx: CompensationContext) => Promise<Result<TOutput>>;
25
- compensate?: (output: TOutput, ctx: CompensationContext) => Promise<void>;
26
- }
@@ -1,13 +0,0 @@
1
- export interface DatabaseAdapter<TDatabase = unknown, TTransaction = unknown> {
2
- provider: string;
3
- db: TDatabase;
4
- transaction<T>(fn: (tx: TTransaction) => Promise<T>): Promise<T>;
5
- }
6
-
7
- export interface DatabaseConnectionFactoryInput {
8
- adapter: DatabaseAdapter;
9
- }
10
-
11
- export function createDatabaseConnection(input: DatabaseConnectionFactoryInput): DatabaseAdapter {
12
- return input.adapter;
13
- }
@@ -1,56 +0,0 @@
1
- /**
2
- * Drizzle Database Type Definitions
3
- *
4
- * This module provides type-safe, driver-agnostic database access for
5
- * PostgreSQL repositories.
6
- *
7
- * We use `PgDatabase` from `drizzle-orm/pg-core` — the base class that all
8
- * PostgreSQL drivers extend (postgres-js, pglite, node-postgres, bun-sql).
9
- * This means:
10
- *
11
- * - Repositories accept any PG driver without casts
12
- * - PGlite in tests and postgres-js in production use the same type
13
- * - Row types are fully inferred from pgTable schema definitions
14
- * - No coupling to any specific driver package
15
- */
16
-
17
- import type { PgDatabase, PgQueryResultHKT } from "drizzle-orm/pg-core";
18
- import * as schema from "./schema.js";
19
-
20
- /**
21
- * Combined schema type for type inference
22
- */
23
- export type Schema = typeof schema;
24
-
25
- /**
26
- * Driver-agnostic PostgreSQL database instance with full schema type
27
- * information.
28
- *
29
- * Both `PostgresJsDatabase<Schema>` and `PgliteDatabase<Schema>` are
30
- * assignable to this type, so repositories work identically in production
31
- * and tests without any casts.
32
- */
33
- export type DrizzleDatabase = PgDatabase<PgQueryResultHKT, Schema>;
34
-
35
- /**
36
- * Transaction type extracted from the database type.
37
- * Used when operating within a transaction context.
38
- *
39
- * This type is derived from the transaction callback parameter:
40
- * db.transaction(async (tx) => { ... })
41
- * ^^-- This is DrizzleTx
42
- */
43
- export type DrizzleTx = Parameters<
44
- Parameters<DrizzleDatabase["transaction"]>[0]
45
- >[0];
46
-
47
- /**
48
- * Union type for database or transaction - repositories can accept either.
49
- * Both have the same query builder interface.
50
- */
51
- export type DbOrTx = DrizzleDatabase | DrizzleTx;
52
-
53
- /**
54
- * Re-export schema for convenience
55
- */
56
- export { schema };
@@ -1,76 +0,0 @@
1
- /**
2
- * Programmatic database schema management.
3
- *
4
- * For npm consumers who don't have access to the raw .ts schema files,
5
- * this module provides:
6
- *
7
- * 1. `getSchemaFiles()` — returns schema module paths for use in drizzle.config.ts
8
- * 2. `getSchema()` — returns the combined Drizzle schema object
9
- * 3. `pushSchema()` — programmatic push (creates tables if not exist)
10
- *
11
- * Usage in consumer's drizzle.config.ts:
12
- *
13
- * import { getSchema } from "@unifiedcommerce/core";
14
- * import { defineConfig } from "drizzle-kit";
15
- *
16
- * export default defineConfig({
17
- * dialect: "postgresql",
18
- * schema: getSchema(),
19
- * dbCredentials: { url: process.env.DATABASE_URL! },
20
- * });
21
- */
22
-
23
- import type { CommerceConfig } from "../../config/types.js";
24
- import * as schema from "./schema.js";
25
-
26
- /**
27
- * Returns the combined Drizzle schema object with all table definitions.
28
- * Use this in your own drizzle.config.ts or for programmatic schema inspection.
29
- */
30
- export function getSchema() {
31
- return schema;
32
- }
33
-
34
- /**
35
- * Returns core schema merged with all plugin schemas from `config.customSchemas[]`.
36
- * Throws if a plugin table name collides with a core table name.
37
- */
38
- export function buildSchema(config?: CommerceConfig): Record<string, unknown> {
39
- const merged: Record<string, unknown> = { ...schema };
40
-
41
- if (!config?.customSchemas?.length) return merged;
42
-
43
- const coreKeys = new Set(Object.keys(schema));
44
-
45
- for (const pluginSchema of config.customSchemas) {
46
- for (const [key, value] of Object.entries(pluginSchema)) {
47
- if (coreKeys.has(key)) {
48
- throw new Error(
49
- `Plugin schema name collision: "${key}" already exists in core schema`,
50
- );
51
- }
52
- if (key in merged) {
53
- throw new Error(
54
- `Plugin schema name collision: "${key}" is defined by multiple plugins`,
55
- );
56
- }
57
- merged[key] = value;
58
- }
59
- }
60
-
61
- return merged;
62
- }
63
-
64
- /**
65
- * Returns a list of all schema table names defined by the commerce engine.
66
- */
67
- export function getTableNames(): string[] {
68
- return Object.entries(schema)
69
- .filter(
70
- ([_, value]) =>
71
- value != null &&
72
- typeof value === "object" &&
73
- "getSQL" in (value as object),
74
- )
75
- .map(([key]) => key);
76
- }