@unifiedcommerce/core 0.2.0 → 0.2.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 (186) hide show
  1. package/package.json +2 -1
  2. package/src/adapters/console-email.ts +43 -0
  3. package/src/auth/access.ts +187 -0
  4. package/src/auth/auth-schema.ts +139 -0
  5. package/src/auth/middleware.ts +161 -0
  6. package/src/auth/org.ts +41 -0
  7. package/src/auth/permissions.ts +28 -0
  8. package/src/auth/setup.ts +171 -0
  9. package/src/auth/system-actor.ts +19 -0
  10. package/src/auth/types.ts +10 -0
  11. package/src/config/defaults.ts +82 -0
  12. package/src/config/define-config.ts +53 -0
  13. package/src/config/types.ts +301 -0
  14. package/src/generated/plugin-capabilities.d.ts +20 -0
  15. package/src/generated/plugin-manifest.ts +23 -0
  16. package/src/generated/plugin-repositories.d.ts +20 -0
  17. package/src/hooks/checkout-completion.ts +262 -0
  18. package/src/hooks/checkout.ts +677 -0
  19. package/src/hooks/order-emails.ts +62 -0
  20. package/src/index.ts +215 -0
  21. package/src/interfaces/mcp/agent-prompt.ts +174 -0
  22. package/src/interfaces/mcp/context-enrichment.ts +177 -0
  23. package/src/interfaces/mcp/server.ts +47 -0
  24. package/src/interfaces/mcp/tool-builder.ts +261 -0
  25. package/src/interfaces/mcp/tools/analytics.ts +76 -0
  26. package/src/interfaces/mcp/tools/cart.ts +57 -0
  27. package/src/interfaces/mcp/tools/catalog.ts +299 -0
  28. package/src/interfaces/mcp/tools/index.ts +22 -0
  29. package/src/interfaces/mcp/tools/inventory.ts +161 -0
  30. package/src/interfaces/mcp/tools/orders.ts +104 -0
  31. package/src/interfaces/mcp/tools/pricing.ts +94 -0
  32. package/src/interfaces/mcp/tools/promotions.ts +106 -0
  33. package/src/interfaces/mcp/tools/registry.ts +101 -0
  34. package/src/interfaces/mcp/tools/search.ts +42 -0
  35. package/src/interfaces/mcp/tools/webhooks.ts +48 -0
  36. package/src/interfaces/mcp/transport.ts +128 -0
  37. package/src/interfaces/rest/customer-portal.ts +299 -0
  38. package/src/interfaces/rest/index.ts +74 -0
  39. package/src/interfaces/rest/router.ts +333 -0
  40. package/src/interfaces/rest/routes/admin-jobs.ts +58 -0
  41. package/src/interfaces/rest/routes/audit.ts +50 -0
  42. package/src/interfaces/rest/routes/carts.ts +89 -0
  43. package/src/interfaces/rest/routes/catalog.ts +493 -0
  44. package/src/interfaces/rest/routes/checkout.ts +284 -0
  45. package/src/interfaces/rest/routes/inventory.ts +70 -0
  46. package/src/interfaces/rest/routes/media.ts +86 -0
  47. package/src/interfaces/rest/routes/orders.ts +78 -0
  48. package/src/interfaces/rest/routes/payments.ts +60 -0
  49. package/src/interfaces/rest/routes/pricing.ts +57 -0
  50. package/src/interfaces/rest/routes/promotions.ts +93 -0
  51. package/src/interfaces/rest/routes/search.ts +71 -0
  52. package/src/interfaces/rest/routes/webhooks.ts +46 -0
  53. package/src/interfaces/rest/schemas/admin-jobs.ts +40 -0
  54. package/src/interfaces/rest/schemas/audit.ts +46 -0
  55. package/src/interfaces/rest/schemas/carts.ts +125 -0
  56. package/src/interfaces/rest/schemas/catalog.ts +450 -0
  57. package/src/interfaces/rest/schemas/checkout.ts +66 -0
  58. package/src/interfaces/rest/schemas/customer-portal.ts +195 -0
  59. package/src/interfaces/rest/schemas/inventory.ts +138 -0
  60. package/src/interfaces/rest/schemas/media.ts +75 -0
  61. package/src/interfaces/rest/schemas/orders.ts +104 -0
  62. package/src/interfaces/rest/schemas/pricing.ts +80 -0
  63. package/src/interfaces/rest/schemas/promotions.ts +110 -0
  64. package/src/interfaces/rest/schemas/responses.ts +85 -0
  65. package/src/interfaces/rest/schemas/search.ts +58 -0
  66. package/src/interfaces/rest/schemas/shared.ts +62 -0
  67. package/src/interfaces/rest/schemas/webhooks.ts +68 -0
  68. package/src/interfaces/rest/utils.ts +104 -0
  69. package/src/interfaces/rest/webhook-router.ts +50 -0
  70. package/src/kernel/compensation/executor.ts +61 -0
  71. package/src/kernel/compensation/types.ts +26 -0
  72. package/src/kernel/database/adapter.ts +21 -0
  73. package/src/kernel/database/drizzle-db.ts +56 -0
  74. package/src/kernel/database/migrate.ts +76 -0
  75. package/src/kernel/database/plugin-types.ts +34 -0
  76. package/src/kernel/database/schema.ts +49 -0
  77. package/src/kernel/database/scoped-db.ts +68 -0
  78. package/src/kernel/database/tx-context.ts +46 -0
  79. package/src/kernel/error-mapper.ts +15 -0
  80. package/src/kernel/errors.ts +89 -0
  81. package/src/kernel/factory/repository-factory.ts +244 -0
  82. package/src/kernel/hooks/create-context.ts +43 -0
  83. package/src/kernel/hooks/executor.ts +88 -0
  84. package/src/kernel/hooks/registry.ts +74 -0
  85. package/src/kernel/hooks/types.ts +52 -0
  86. package/src/kernel/http-error.ts +44 -0
  87. package/src/kernel/jobs/adapter.ts +36 -0
  88. package/src/kernel/jobs/drizzle-adapter.ts +58 -0
  89. package/src/kernel/jobs/runner.ts +153 -0
  90. package/src/kernel/jobs/schema.ts +46 -0
  91. package/src/kernel/jobs/types.ts +30 -0
  92. package/src/kernel/local-api.ts +187 -0
  93. package/src/kernel/plugin/manifest.ts +271 -0
  94. package/src/kernel/query/executor.ts +184 -0
  95. package/src/kernel/query/registry.ts +46 -0
  96. package/src/kernel/result.ts +33 -0
  97. package/src/kernel/schema/extra-columns.ts +37 -0
  98. package/src/kernel/service-registry.ts +76 -0
  99. package/src/kernel/service-timing.ts +89 -0
  100. package/src/kernel/state-machine/machine.ts +101 -0
  101. package/src/modules/analytics/drizzle-adapter.ts +426 -0
  102. package/src/modules/analytics/hooks.ts +11 -0
  103. package/src/modules/analytics/models.ts +125 -0
  104. package/src/modules/analytics/repository/index.ts +6 -0
  105. package/src/modules/analytics/service.ts +245 -0
  106. package/src/modules/analytics/types.ts +180 -0
  107. package/src/modules/audit/hooks.ts +78 -0
  108. package/src/modules/audit/schema.ts +33 -0
  109. package/src/modules/audit/service.ts +151 -0
  110. package/src/modules/cart/access.ts +27 -0
  111. package/src/modules/cart/matcher.ts +26 -0
  112. package/src/modules/cart/repository/index.ts +234 -0
  113. package/src/modules/cart/schema.ts +42 -0
  114. package/src/modules/cart/schemas.ts +38 -0
  115. package/src/modules/cart/service.ts +541 -0
  116. package/src/modules/catalog/repository/index.ts +772 -0
  117. package/src/modules/catalog/schema.ts +203 -0
  118. package/src/modules/catalog/schemas.ts +104 -0
  119. package/src/modules/catalog/service.ts +1544 -0
  120. package/src/modules/customers/repository/index.ts +327 -0
  121. package/src/modules/customers/schema.ts +64 -0
  122. package/src/modules/customers/service.ts +171 -0
  123. package/src/modules/fulfillment/repository/index.ts +426 -0
  124. package/src/modules/fulfillment/schema.ts +101 -0
  125. package/src/modules/fulfillment/service.ts +555 -0
  126. package/src/modules/fulfillment/types.ts +59 -0
  127. package/src/modules/inventory/repository/index.ts +509 -0
  128. package/src/modules/inventory/schema.ts +94 -0
  129. package/src/modules/inventory/schemas.ts +38 -0
  130. package/src/modules/inventory/service.ts +490 -0
  131. package/src/modules/media/adapter.ts +17 -0
  132. package/src/modules/media/repository/index.ts +274 -0
  133. package/src/modules/media/schema.ts +41 -0
  134. package/src/modules/media/service.ts +151 -0
  135. package/src/modules/orders/repository/index.ts +287 -0
  136. package/src/modules/orders/schema.ts +66 -0
  137. package/src/modules/orders/service.ts +619 -0
  138. package/src/modules/orders/stale-order-cleanup.ts +76 -0
  139. package/src/modules/organization/service.ts +191 -0
  140. package/src/modules/payments/adapter.ts +47 -0
  141. package/src/modules/payments/repository/index.ts +6 -0
  142. package/src/modules/payments/service.ts +107 -0
  143. package/src/modules/pricing/repository/index.ts +291 -0
  144. package/src/modules/pricing/schema.ts +71 -0
  145. package/src/modules/pricing/schemas.ts +38 -0
  146. package/src/modules/pricing/service.ts +494 -0
  147. package/src/modules/promotions/repository/index.ts +325 -0
  148. package/src/modules/promotions/schema.ts +62 -0
  149. package/src/modules/promotions/schemas.ts +38 -0
  150. package/src/modules/promotions/service.ts +598 -0
  151. package/src/modules/search/adapter.ts +57 -0
  152. package/src/modules/search/hooks.ts +12 -0
  153. package/src/modules/search/repository/index.ts +6 -0
  154. package/src/modules/search/service.ts +315 -0
  155. package/src/modules/shipping/calculator.ts +188 -0
  156. package/src/modules/shipping/repository/index.ts +6 -0
  157. package/src/modules/shipping/service.ts +51 -0
  158. package/src/modules/tax/adapter.ts +60 -0
  159. package/src/modules/tax/repository/index.ts +6 -0
  160. package/src/modules/tax/service.ts +53 -0
  161. package/src/modules/webhooks/hook.ts +34 -0
  162. package/src/modules/webhooks/repository/index.ts +278 -0
  163. package/src/modules/webhooks/schema.ts +56 -0
  164. package/src/modules/webhooks/service.ts +117 -0
  165. package/src/modules/webhooks/signing.ts +6 -0
  166. package/src/modules/webhooks/ssrf-guard.ts +71 -0
  167. package/src/modules/webhooks/tasks.ts +52 -0
  168. package/src/modules/webhooks/worker.ts +134 -0
  169. package/src/runtime/commerce.ts +145 -0
  170. package/src/runtime/kernel.ts +426 -0
  171. package/src/runtime/logger.ts +36 -0
  172. package/src/runtime/server.ts +355 -0
  173. package/src/runtime/shutdown.ts +43 -0
  174. package/src/test-utils/create-pglite-adapter.ts +129 -0
  175. package/src/test-utils/create-plugin-test-app.ts +128 -0
  176. package/src/test-utils/create-repository-test-harness.ts +16 -0
  177. package/src/test-utils/create-test-config.ts +190 -0
  178. package/src/test-utils/create-test-kernel.ts +7 -0
  179. package/src/test-utils/create-test-plugin-context.ts +75 -0
  180. package/src/test-utils/rest-api-test-utils.ts +265 -0
  181. package/src/test-utils/test-actors.ts +62 -0
  182. package/src/test-utils/typed-hooks.ts +54 -0
  183. package/src/types/commerce-types.ts +34 -0
  184. package/src/utils/id.ts +3 -0
  185. package/src/utils/logger.ts +18 -0
  186. package/src/utils/pagination.ts +22 -0
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Order lifecycle email notifications.
3
+ *
4
+ * Sends emails on status changes: confirmed, fulfilled, cancelled, refunded.
5
+ * Registered as an orders.afterStatusChange hook.
6
+ */
7
+
8
+ import type { AfterHook } from "../kernel/hooks/types.js";
9
+
10
+ interface StatusChangeResult {
11
+ orderId: string;
12
+ customerId?: string | null;
13
+ newStatus: string;
14
+ previousStatus: string;
15
+ }
16
+
17
+ export const sendOrderStatusEmail: AfterHook<StatusChangeResult> = async ({
18
+ result,
19
+ context,
20
+ }) => {
21
+ const email = context.services.email as
22
+ | { send(input: { template: string; to: string; data?: Record<string, unknown> }): Promise<void> }
23
+ | undefined;
24
+
25
+ if (!email?.send) return;
26
+
27
+ // Only send for customer-facing status changes
28
+ const notifiableStatuses = ["confirmed", "processing", "fulfilled", "cancelled", "refunded"];
29
+ if (!notifiableStatuses.includes(result.newStatus)) return;
30
+
31
+ // Look up customer email
32
+ const customerId = result.customerId;
33
+ if (!customerId) return;
34
+
35
+ const customers = context.services.customers as
36
+ | { getByUserId(id: string, actor?: unknown): Promise<{ ok: boolean; value?: { email?: string | null } }> }
37
+ | undefined;
38
+
39
+ if (!customers) return;
40
+
41
+ try {
42
+ const customer = await customers.getByUserId(customerId, context.actor);
43
+ if (!customer.ok || !customer.value?.email) return;
44
+
45
+ await email.send({
46
+ template: "order-status-change",
47
+ to: customer.value.email,
48
+ data: {
49
+ orderId: result.orderId,
50
+ newStatus: result.newStatus,
51
+ previousStatus: result.previousStatus,
52
+ },
53
+ });
54
+ } catch (err) {
55
+ // Email failure must not break the order flow
56
+ context.logger.warn("Order status email failed", {
57
+ orderId: result.orderId,
58
+ newStatus: result.newStatus,
59
+ error: err instanceof Error ? err.message : String(err),
60
+ });
61
+ }
62
+ };
package/src/index.ts ADDED
@@ -0,0 +1,215 @@
1
+ export { defineConfig } from "./config/define-config.js";
2
+ export type {
3
+ CommerceConfig,
4
+ CommercePlugin,
5
+ MCPResource,
6
+ MCPTool,
7
+ } from "./config/types.js";
8
+ export { defineCommercePlugin } from "./kernel/plugin/manifest.js";
9
+ export type {
10
+ CommercePluginManifest,
11
+ PluginContext,
12
+ PluginHookRegistration,
13
+ PluginLogger,
14
+ PluginPermission,
15
+ PluginRouteRegistration,
16
+ } from "./kernel/plugin/manifest.js";
17
+
18
+ export { router } from "./interfaces/rest/router.js";
19
+ export { toolBuilder } from "./interfaces/mcp/tool-builder.js";
20
+ export { webhookRouter, type WebhookRouterResult } from "./interfaces/rest/webhook-router.js";
21
+ export { isPrivateUrl, isPrivateIp } from "./modules/webhooks/ssrf-guard.js";
22
+ export { createServer } from "./runtime/server.js";
23
+ export { createLogger } from "./runtime/logger.js";
24
+ export type { Logger as PinoLogger } from "./runtime/logger.js";
25
+ export { setupGracefulShutdown } from "./runtime/shutdown.js";
26
+ export { createKernel } from "./runtime/kernel.js";
27
+ export type { Kernel } from "./runtime/kernel.js";
28
+ export { createTestKernel } from "./test-utils/create-test-kernel.js";
29
+ export { createTestPluginContext } from "./test-utils/create-test-plugin-context.js";
30
+ export { createRepositoryTestHarness } from "./test-utils/create-repository-test-harness.js";
31
+ export { createPluginTestApp, type PluginTestApp, type TestAppEnv } from "./test-utils/create-plugin-test-app.js";
32
+ export {
33
+ testAdminActor, testStaffActor, testCustomerActor, testNoPermActor,
34
+ jsonHeaders,
35
+ } from "./test-utils/test-actors.js";
36
+ export { beforeHook, afterHook } from "./test-utils/typed-hooks.js";
37
+
38
+ export type { Actor } from "./auth/types.js";
39
+ export { resolveOrgId, ensureDefaultOrg, DEFAULT_ORG_ID } from "./auth/org.js";
40
+ export { OrganizationService } from "./modules/organization/service.js";
41
+ export { createScopedDb } from "./kernel/database/scoped-db.js";
42
+ export { assertOwnership, assertPermission } from "./auth/permissions.js";
43
+ export type { AccessResult, AccessContext, AccessFn, WhereClause } from "./auth/access.js";
44
+ export {
45
+ accessOR,
46
+ accessAND,
47
+ conditional,
48
+ isAdmin,
49
+ isAuthenticated,
50
+ isDocumentOwner,
51
+ publicAccess,
52
+ denyAll,
53
+ } from "./auth/access.js";
54
+
55
+ export { HookRegistry } from "./kernel/hooks/registry.js";
56
+ export type {
57
+ BeforeHook,
58
+ AfterHook,
59
+ HookContext,
60
+ HookOperation,
61
+ HookOrigin,
62
+ Logger,
63
+ ServiceContainer,
64
+ } from "./kernel/hooks/types.js";
65
+ export { runBeforeHooks, runAfterHooks } from "./kernel/hooks/executor.js";
66
+ export { createHookContext } from "./kernel/hooks/create-context.js";
67
+ export type { CreateHookContextArgs } from "./kernel/hooks/create-context.js";
68
+ export type { JobsAdapter, EnqueueOptions } from "./kernel/jobs/adapter.js";
69
+ export { NullJobsAdapter } from "./kernel/jobs/adapter.js";
70
+ export { DrizzleJobsAdapter } from "./kernel/jobs/drizzle-adapter.js";
71
+ export { runPendingJobs } from "./kernel/jobs/runner.js";
72
+ export type { RunPendingJobsArgs } from "./kernel/jobs/runner.js";
73
+ export type {
74
+ TaskDefinition,
75
+ TaskContext,
76
+ TaskRetryConfig,
77
+ } from "./kernel/jobs/types.js";
78
+
79
+ export { createLocalAPI, LocalAPI } from "./kernel/local-api.js";
80
+ export type { CommerceLocalAPI, LocalAPIOptions } from "./kernel/local-api.js";
81
+ export { createCommerce } from "./runtime/commerce.js";
82
+ export type { CommerceInstance } from "./runtime/commerce.js";
83
+
84
+ export { createAuditService, createNullAuditService } from "./modules/audit/service.js";
85
+ export type {
86
+ AuditService,
87
+ AuditEntry,
88
+ RecordArgs,
89
+ ListForEntityArgs,
90
+ } from "./modules/audit/service.js";
91
+
92
+ export type { Result, PluginResult, PluginResultErr } from "./kernel/result.js";
93
+ export { Ok, Err, PluginErr } from "./kernel/result.js";
94
+ export type { PluginDb, PluginTxFn } from "./kernel/database/plugin-types.js";
95
+ export type { ServiceRegistry } from "./kernel/service-registry.js";
96
+ export { toHttpError, type HttpErrorResponse } from "./kernel/http-error.js";
97
+ export { withTiming } from "./kernel/service-timing.js";
98
+
99
+ export {
100
+ CommerceNotFoundError,
101
+ CommerceValidationError,
102
+ CommerceForbiddenError,
103
+ CommerceConflictError,
104
+ CommerceInvalidTransitionError,
105
+ } from "./kernel/errors.js";
106
+
107
+ export { mapErrorToStatus } from "./kernel/error-mapper.js";
108
+
109
+ export {
110
+ canTransition,
111
+ assertTransition,
112
+ orderStateMachine,
113
+ extendOrderStateMachine,
114
+ } from "./kernel/state-machine/machine.js";
115
+
116
+ export type {
117
+ PaymentAdapter,
118
+ PaymentCapture,
119
+ PaymentIntent,
120
+ PaymentRefund,
121
+ PaymentWebhookEvent,
122
+ } from "./modules/payments/adapter.js";
123
+ export type { StorageAdapter } from "./modules/media/adapter.js";
124
+ export type {
125
+ SearchAdapter,
126
+ SearchDocument,
127
+ SearchFilters,
128
+ SearchHit,
129
+ SearchQueryParams,
130
+ SearchQueryResult,
131
+ SearchSuggestParams,
132
+ } from "./modules/search/adapter.js";
133
+ export type { DatabaseAdapter } from "./kernel/database/adapter.js";
134
+ export {
135
+ createTxContext,
136
+ reuseOrCreateTxContext,
137
+ withTransaction,
138
+ } from "./kernel/database/tx-context.js";
139
+ export type {
140
+ TxContext,
141
+ WithTransactionOptions,
142
+ } from "./kernel/database/tx-context.js";
143
+ export type {
144
+ TaxAdapter,
145
+ TaxAddress,
146
+ TaxCalculationParams,
147
+ TaxCalculationResult,
148
+ TaxLineItem,
149
+ TaxReportParams,
150
+ TaxVoidParams,
151
+ } from "./modules/tax/adapter.js";
152
+
153
+ export { getSchema, buildSchema, getTableNames } from "./kernel/database/migrate.js";
154
+ export { consoleEmailAdapter } from "./adapters/console-email.js";
155
+
156
+ export { runCompensationChain } from "./kernel/compensation/executor.js";
157
+ export type {
158
+ CompensationContext,
159
+ Step,
160
+ } from "./kernel/compensation/types.js";
161
+
162
+ export { createRepository } from "./kernel/factory/repository-factory.js";
163
+ export type {
164
+ BaseRepository,
165
+ SoftDeletableRepository,
166
+ RepositoryFor,
167
+ Filters,
168
+ FindOptions,
169
+ } from "./kernel/factory/repository-factory.js";
170
+
171
+ export { mergeExtraColumns } from "./kernel/schema/extra-columns.js";
172
+ export type { ExtraColumnsOption } from "./kernel/schema/extra-columns.js";
173
+
174
+ export type { CartItemMatcher } from "./modules/cart/matcher.js";
175
+ export { defaultCartItemMatcher } from "./modules/cart/matcher.js";
176
+ export { canAccessCart } from "./modules/cart/access.js";
177
+
178
+ export { QueryRegistry } from "./kernel/query/registry.js";
179
+ export { executeQuery } from "./kernel/query/executor.js";
180
+ export type {
181
+ RelationDefinition,
182
+ EntityDefinition,
183
+ } from "./kernel/query/registry.js";
184
+ export type { QueryInput, QueryResult } from "./kernel/query/executor.js";
185
+
186
+ export type { CommerceModuleTypes } from "./types/commerce-types.js";
187
+
188
+ export { staleOrderCleanupTask } from "./modules/orders/stale-order-cleanup.js";
189
+ export {
190
+ COMMERCE_AGENT_SYSTEM_PROMPT,
191
+ COMMERCE_AGENT_SYSTEM_PROMPT_COMPACT,
192
+ } from "./interfaces/mcp/agent-prompt.js";
193
+
194
+ export { DrizzleAnalyticsAdapter } from "./modules/analytics/drizzle-adapter.js";
195
+ export { BUILTIN_ANALYTICS_MODELS } from "./modules/analytics/models.js";
196
+ export { buildAnalyticsScope } from "./modules/analytics/types.js";
197
+ export type {
198
+ AnalyticsAdapter,
199
+ AnalyticsQueryParams,
200
+ AnalyticsQueryResult,
201
+ AnalyticsMeta,
202
+ AnalyticsModelDefinition,
203
+ AnalyticsScope,
204
+ AnalyticsModel,
205
+ AnalyticsScopeRule,
206
+ AnalyticsMeasure,
207
+ AnalyticsDimension,
208
+ AnalyticsJoin,
209
+ // Deprecated aliases (remove in next major)
210
+ CubeScopeRule,
211
+ CubeDefinition,
212
+ MeasureDefinition,
213
+ DimensionDefinition,
214
+ JoinDefinition,
215
+ } from "./modules/analytics/types.js";
@@ -0,0 +1,174 @@
1
+ /**
2
+ * System prompt for AI agents interacting with the UnifiedCommerce Engine via MCP.
3
+ *
4
+ * This prompt grounds the agent in the engine's semantic layer, preventing
5
+ * hallucination on metric definitions and guiding correct tool usage.
6
+ *
7
+ * Usage:
8
+ * import { COMMERCE_AGENT_SYSTEM_PROMPT } from "@unifiedcommerce/core";
9
+ * // Pass as the system prompt when creating a Claude conversation
10
+ */
11
+
12
+ export const COMMERCE_AGENT_SYSTEM_PROMPT = `You are an AI commerce assistant with access to a live e-commerce store via the UnifiedCommerce Engine. You can browse the catalog, manage inventory, create orders, query analytics, and manage marketplace operations.
13
+
14
+ ## CRITICAL: Analytics Grounding Rules
15
+
16
+ You have access to a semantic analytics layer with predefined metrics. NEVER guess metric definitions or write raw calculations. Instead:
17
+
18
+ 1. ALWAYS call \`analytics_meta\` first to discover what metrics are available before answering any analytics question.
19
+ 2. ONLY use measures and dimensions returned by \`analytics_meta\`. If a user asks about a metric that doesn't exist (e.g., "customer acquisition cost"), say: "That metric isn't currently tracked. Here are the metrics I can report on: [list available measures]."
20
+ 3. NEVER return 0 as an answer without verifying it's actually zero — if analytics_query returns an error, tell the user the metric isn't available.
21
+ 4. When reporting monetary values, all amounts are in the smallest currency unit (cents). Divide by 100 for display: 242626602 cents = $2,426,266.02.
22
+
23
+ ## Available Analytics Measures
24
+
25
+ ### Orders
26
+ - \`Orders.count\` — Total number of orders
27
+ - \`Orders.revenue\` — Total revenue (SUM of grand_total, in cents)
28
+ - \`Orders.averageOrderValue\` — Average order value (in cents)
29
+ - \`Orders.subtotalRevenue\` — Revenue before tax/shipping/discounts
30
+ - \`Orders.taxCollected\` — Total tax collected
31
+ - \`Orders.shippingRevenue\` — Total shipping charges
32
+ - \`Orders.discountsGiven\` — Total discounts applied
33
+ - \`Orders.uniqueCustomers\` — Distinct customers who placed orders
34
+
35
+ ### Order Line Items
36
+ - \`OrderLineItems.count\` — Total line items across all orders
37
+ - \`OrderLineItems.itemsSold\` — Total units sold (SUM of quantity)
38
+ - \`OrderLineItems.lineItemRevenue\` — Revenue by product
39
+ - \`OrderLineItems.averageUnitPrice\` — Average price per unit
40
+
41
+ ### Inventory
42
+ - \`Inventory.totalOnHand\` — Total units in warehouses
43
+ - \`Inventory.totalReserved\` — Units reserved for pending orders
44
+ - \`Inventory.totalAvailable\` — Units available for sale (on_hand - reserved)
45
+ - \`Inventory.inventoryValue\` — Total value of inventory (qty × unit cost)
46
+ - \`Inventory.lowStockCount\` — Items below reorder threshold
47
+
48
+ ### Customers
49
+ - \`Customers.customerCount\` — Total registered customers
50
+ - \`Customers.newCustomers\` — New customer count
51
+ - \`Customers.returningCustomers\` — Customers with more than one order
52
+
53
+ ## Available Dimensions (for GROUP BY)
54
+
55
+ - \`Orders.status\` — pending, confirmed, processing, fulfilled, cancelled, refunded
56
+ - \`Orders.placedAt\` — Time dimension (supports day/week/month/year granularity)
57
+ - \`Orders.currency\` — Currency code
58
+ - \`OrderLineItems.title\` — Product name (use for "top products" queries)
59
+ - \`OrderLineItems.entityType\` — Product type
60
+ - \`Inventory.warehouseId\` — Warehouse UUID
61
+ - \`Inventory.entityId\` — Product UUID
62
+ - \`Customers.createdAt\` — Customer registration date
63
+
64
+ ## Query Examples
65
+
66
+ **"What was our revenue last month?"**
67
+ \`\`\`json
68
+ {
69
+ "measures": ["Orders.revenue", "Orders.count"],
70
+ "timeDimensions": [{
71
+ "dimension": "Orders.placedAt",
72
+ "granularity": "month",
73
+ "dateRange": "last month"
74
+ }]
75
+ }
76
+ \`\`\`
77
+
78
+ **"Top 5 products by units sold"**
79
+ \`\`\`json
80
+ {
81
+ "measures": ["OrderLineItems.itemsSold"],
82
+ "dimensions": ["OrderLineItems.title"],
83
+ "order": { "OrderLineItems.itemsSold": "desc" },
84
+ "limit": 5
85
+ }
86
+ \`\`\`
87
+
88
+ **"Revenue by status"**
89
+ \`\`\`json
90
+ {
91
+ "measures": ["Orders.revenue", "Orders.count"],
92
+ "dimensions": ["Orders.status"]
93
+ }
94
+ \`\`\`
95
+
96
+ **"Monthly revenue trend for 2025"**
97
+ \`\`\`json
98
+ {
99
+ "measures": ["Orders.revenue"],
100
+ "timeDimensions": [{
101
+ "dimension": "Orders.placedAt",
102
+ "granularity": "month",
103
+ "dateRange": ["2025-01-01", "2025-12-31"]
104
+ }]
105
+ }
106
+ \`\`\`
107
+
108
+ ## Available Tools
109
+
110
+ ### Catalog
111
+ - \`catalog_search\` — Search products with filters (type, query, category, brand)
112
+ - \`catalog_create_entity\` — Create a new product
113
+
114
+ ### Inventory
115
+ - \`inventory_check\` — Check available stock for product(s)
116
+ - \`inventory_adjust\` — Add or remove stock
117
+
118
+ ### Cart & Orders
119
+ - \`cart_create\` — Create a shopping cart
120
+ - \`cart_add_item\` — Add item to cart
121
+ - \`order_get\` — Get order details by ID
122
+ - \`order_list\` — List orders with pagination
123
+
124
+ ### Analytics
125
+ - \`analytics_query\` — Query analytics with measures, dimensions, filters, time dimensions
126
+ - \`analytics_meta\` — List all available measures, dimensions, and models
127
+
128
+ ### Marketplace (if marketplace plugin is enabled)
129
+ - \`marketplace_vendor_list\` — List marketplace vendors
130
+ - \`marketplace_vendor_performance\` — Get vendor metrics and rating
131
+ - \`marketplace_vendor_balance\` — Get vendor balance and ledger
132
+ - \`marketplace_suborder_update\` — Transition sub-order status
133
+ - \`marketplace_dispute_summary\` — List open disputes
134
+ - \`marketplace_payout_run\` — Trigger vendor payout cycle
135
+ - \`marketplace_commission_preview\` — Preview commission rate
136
+ - \`marketplace_rfq_list\` — List open RFQs (B2B)
137
+
138
+ ## Available Resources
139
+
140
+ - \`commerce://schema/entity-types\` — Product type definitions with fields and variants
141
+ - \`commerce://schema/order-states\` — Order state machine with valid transitions
142
+
143
+ ## Order State Machine
144
+
145
+ Orders follow this lifecycle:
146
+ \`\`\`
147
+ pending → confirmed → processing → [partially_fulfilled | fulfilled] → refunded
148
+ ↘ cancelled (from any non-terminal state)
149
+ \`\`\`
150
+
151
+ When answering questions about order statuses, use the state machine to determine valid transitions. Don't suggest transitions that aren't allowed.
152
+
153
+ ## Response Guidelines
154
+
155
+ 1. **Be specific with numbers.** Don't say "revenue increased" — say "revenue was $42,001.03 in November, up 43% from October's $29,799.44."
156
+ 2. **Always include units.** Monetary values in dollars (divide cents by 100), quantities as "units", time periods explicitly.
157
+ 3. **Cite the source.** When reporting analytics, mention which measure you queried: "Based on Orders.revenue grouped by month..."
158
+ 4. **Suggest follow-ups.** After answering, suggest related queries: "Would you like to see this broken down by product? Or compare with the previous quarter?"
159
+ 5. **Handle errors gracefully.** If a tool call fails, explain what went wrong and suggest alternatives.
160
+ `;
161
+
162
+ /**
163
+ * Shorter version for token-constrained contexts.
164
+ */
165
+ export const COMMERCE_AGENT_SYSTEM_PROMPT_COMPACT = `You are an AI commerce assistant with MCP access to a live store.
166
+
167
+ CRITICAL: For analytics, ALWAYS call analytics_meta first to discover available metrics. Only use measures/dimensions listed there. All monetary values are in cents — divide by 100 for display.
168
+
169
+ Key measures: Orders.revenue, Orders.count, Orders.averageOrderValue, OrderLineItems.itemsSold, Inventory.totalAvailable, Customers.customerCount.
170
+ Key dimensions: Orders.status, Orders.placedAt (time), OrderLineItems.title (products).
171
+ Time ranges: "last month", "this month", "Q1 2026", or ["2025-01-01", "2025-12-31"].
172
+
173
+ If a metric doesn't exist, say so and list what IS available. Never guess or return 0 without verification.
174
+ `;
@@ -0,0 +1,177 @@
1
+ import { orderStateMachine } from "../../kernel/state-machine/machine.js";
2
+ import type { Order, OrderLineItem } from "../../modules/orders/repository/index.js";
3
+ import type { Kernel } from "../../runtime/kernel.js";
4
+
5
+ function getAvailableTransitions(status: string): string[] {
6
+ const transitions = (
7
+ orderStateMachine.transitions as Record<string, readonly string[]>
8
+ )[status];
9
+ return transitions ? [...transitions] : [];
10
+ }
11
+
12
+ async function inventorySummary(
13
+ kernel: Kernel,
14
+ entityId: string,
15
+ ): Promise<{
16
+ onHand: number;
17
+ reserved: number;
18
+ available: number;
19
+ lowStock: boolean;
20
+ reorderSuggestionUnits: number;
21
+ }> {
22
+ const result = await kernel.services.inventory.checkMultiple([entityId]);
23
+ const available = result.ok ? (result.value[entityId] ?? 0) : 0;
24
+
25
+ // For low stock detection, we need a simple heuristic since we don't have
26
+ // direct access to all level details from service API. Use available <= 0.
27
+ const lowStock = available <= 0;
28
+
29
+ return {
30
+ onHand: available, // approximation when detailed levels not available
31
+ reserved: 0,
32
+ available,
33
+ lowStock,
34
+ reorderSuggestionUnits: 0,
35
+ };
36
+ }
37
+
38
+ interface AgentQuery {
39
+ tool: string;
40
+ params: Record<string, unknown>;
41
+ }
42
+
43
+ type Enriched<T> = T & {
44
+ _context: {
45
+ summary: string;
46
+ relatedQueries: AgentQuery[];
47
+ [key: string]: unknown;
48
+ };
49
+ };
50
+
51
+ type AgentOrder = Order & { lineItems: OrderLineItem[] };
52
+
53
+ export function enrichOrderForAgent(
54
+ order: AgentOrder,
55
+ permissions: string[],
56
+ ): Enriched<AgentOrder> {
57
+ return {
58
+ ...order,
59
+ _context: {
60
+ availableTransitions: getAvailableTransitions(order.status),
61
+ permittedActions: permissions.filter((permission) =>
62
+ ["orders:update", "orders:read", "orders:cancel"].includes(permission),
63
+ ),
64
+ summary: `Order ${order.orderNumber}: ${order.lineItems.reduce((sum, lineItem) => sum + lineItem.quantity, 0)} item(s) totaling ${order.grandTotal} ${order.currency}. Status: ${order.status}.`,
65
+ relatedQueries: [
66
+ {
67
+ tool: "inventory_check",
68
+ params: {
69
+ entityIds: order.lineItems.map((lineItem) => lineItem.entityId),
70
+ },
71
+ },
72
+ {
73
+ tool: "analytics_query",
74
+ params: {
75
+ measures: ["Orders.revenue"],
76
+ dimensions: ["Orders.status"],
77
+ filters: [
78
+ { member: "Orders.id", operator: "equals", values: [order.id] },
79
+ ],
80
+ },
81
+ },
82
+ ],
83
+ },
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Enriches an entity with contextual information for AI agents.
89
+ * Accepts both SellableEntityRecord and Drizzle-inferred types.
90
+ * Now async — uses kernel services instead of RuntimeState.
91
+ */
92
+ export async function enrichEntityForAgent<
93
+ T extends { id: string; slug: string },
94
+ >(entity: T, kernel: Kernel): Promise<Enriched<T>> {
95
+ const inventory = await inventorySummary(kernel, entity.id);
96
+
97
+ // Get entity details with variants to count them
98
+ const entityResult = await kernel.services.catalog.getById(entity.id, {
99
+ includeVariants: true,
100
+ });
101
+ const variantCount =
102
+ entityResult.ok && entityResult.value.variants
103
+ ? entityResult.value.variants.length
104
+ : 0;
105
+
106
+ return {
107
+ ...entity,
108
+ _context: {
109
+ summary: `${entity.slug} has ${variantCount} variant(s), ${inventory.available} available units.`,
110
+ stockStatus: inventory.lowStock
111
+ ? "low"
112
+ : inventory.available > 0
113
+ ? "in_stock"
114
+ : "out_of_stock",
115
+ variantCount,
116
+ relatedQueries: [
117
+ {
118
+ tool: "inventory_check",
119
+ params: { entityIds: [entity.id] },
120
+ },
121
+ {
122
+ tool: "analytics_query",
123
+ params: {
124
+ measures: ["OrderLineItems.itemsSold"],
125
+ dimensions: ["OrderLineItems.title"],
126
+ filters: [
127
+ {
128
+ member: "OrderLineItems.title",
129
+ operator: "equals",
130
+ values: [entity.slug],
131
+ },
132
+ ],
133
+ },
134
+ },
135
+ ],
136
+ },
137
+ };
138
+ }
139
+
140
+ export function enrichInventoryForAgent(item: {
141
+ entityId: string;
142
+ available: number;
143
+ reorderThreshold?: number | null;
144
+ reorderQuantity?: number | null;
145
+ }): Enriched<{
146
+ entityId: string;
147
+ available: number;
148
+ reorderThreshold?: number | null;
149
+ reorderQuantity?: number | null;
150
+ }> {
151
+ const lowStock =
152
+ item.reorderThreshold != null
153
+ ? item.available <= item.reorderThreshold
154
+ : item.available <= 0;
155
+
156
+ return {
157
+ ...item,
158
+ _context: {
159
+ lowStockWarning: lowStock,
160
+ reorderSuggestion:
161
+ lowStock && item.reorderQuantity != null
162
+ ? {
163
+ suggestedUnits: item.reorderQuantity,
164
+ }
165
+ : null,
166
+ summary: lowStock
167
+ ? `Entity ${item.entityId} is low on stock (${item.available} available).`
168
+ : `Entity ${item.entityId} has healthy stock (${item.available} available).`,
169
+ relatedQueries: [
170
+ {
171
+ tool: "catalog_search",
172
+ params: { query: item.entityId },
173
+ },
174
+ ],
175
+ },
176
+ };
177
+ }
@@ -0,0 +1,47 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { Kernel } from "../../runtime/kernel.js";
3
+ import { orderStateMachine } from "../../kernel/state-machine/machine.js";
4
+ import { coreTools } from "./tools/index.js";
5
+ import { registerToolsOnServer } from "./tools/registry.js";
6
+
7
+ // ─── Core Tool Registration ─────────────────────────────────────────────────
8
+
9
+ export function registerCoreTools(server: McpServer, kernel: Kernel): void {
10
+ registerToolsOnServer(server, kernel, coreTools);
11
+ }
12
+
13
+ // ─── Core Resource Registration ─────────────────────────────────────────────
14
+
15
+ export function registerCoreResources(server: McpServer, kernel: Kernel): void {
16
+ server.registerResource(
17
+ "Entity Type Schema",
18
+ "commerce://schema/entity-types",
19
+ {
20
+ description: "Complete entity type schema including fields, variants, and fulfillment strategies.",
21
+ mimeType: "application/json",
22
+ },
23
+ async (uri) => ({
24
+ contents: [{
25
+ uri: uri.toString(),
26
+ text: JSON.stringify(kernel.config.entities ?? {}, null, 2),
27
+ mimeType: "application/json",
28
+ }],
29
+ }),
30
+ );
31
+
32
+ server.registerResource(
33
+ "Order State Machine",
34
+ "commerce://schema/order-states",
35
+ {
36
+ description: "Valid order status transitions and the complete state machine definition.",
37
+ mimeType: "application/json",
38
+ },
39
+ async (uri) => ({
40
+ contents: [{
41
+ uri: uri.toString(),
42
+ text: JSON.stringify(orderStateMachine, null, 2),
43
+ mimeType: "application/json",
44
+ }],
45
+ }),
46
+ );
47
+ }