@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.
- package/package.json +2 -1
- package/src/adapters/console-email.ts +43 -0
- package/src/auth/access.ts +187 -0
- package/src/auth/auth-schema.ts +139 -0
- package/src/auth/middleware.ts +161 -0
- package/src/auth/org.ts +41 -0
- package/src/auth/permissions.ts +28 -0
- package/src/auth/setup.ts +171 -0
- package/src/auth/system-actor.ts +19 -0
- package/src/auth/types.ts +10 -0
- package/src/config/defaults.ts +82 -0
- package/src/config/define-config.ts +53 -0
- package/src/config/types.ts +301 -0
- package/src/generated/plugin-capabilities.d.ts +20 -0
- package/src/generated/plugin-manifest.ts +23 -0
- package/src/generated/plugin-repositories.d.ts +20 -0
- package/src/hooks/checkout-completion.ts +262 -0
- package/src/hooks/checkout.ts +677 -0
- package/src/hooks/order-emails.ts +62 -0
- package/src/index.ts +215 -0
- package/src/interfaces/mcp/agent-prompt.ts +174 -0
- package/src/interfaces/mcp/context-enrichment.ts +177 -0
- package/src/interfaces/mcp/server.ts +47 -0
- package/src/interfaces/mcp/tool-builder.ts +261 -0
- package/src/interfaces/mcp/tools/analytics.ts +76 -0
- package/src/interfaces/mcp/tools/cart.ts +57 -0
- package/src/interfaces/mcp/tools/catalog.ts +299 -0
- package/src/interfaces/mcp/tools/index.ts +22 -0
- package/src/interfaces/mcp/tools/inventory.ts +161 -0
- package/src/interfaces/mcp/tools/orders.ts +104 -0
- package/src/interfaces/mcp/tools/pricing.ts +94 -0
- package/src/interfaces/mcp/tools/promotions.ts +106 -0
- package/src/interfaces/mcp/tools/registry.ts +101 -0
- package/src/interfaces/mcp/tools/search.ts +42 -0
- package/src/interfaces/mcp/tools/webhooks.ts +48 -0
- package/src/interfaces/mcp/transport.ts +128 -0
- package/src/interfaces/rest/customer-portal.ts +299 -0
- package/src/interfaces/rest/index.ts +74 -0
- package/src/interfaces/rest/router.ts +333 -0
- package/src/interfaces/rest/routes/admin-jobs.ts +58 -0
- package/src/interfaces/rest/routes/audit.ts +50 -0
- package/src/interfaces/rest/routes/carts.ts +89 -0
- package/src/interfaces/rest/routes/catalog.ts +493 -0
- package/src/interfaces/rest/routes/checkout.ts +284 -0
- package/src/interfaces/rest/routes/inventory.ts +70 -0
- package/src/interfaces/rest/routes/media.ts +86 -0
- package/src/interfaces/rest/routes/orders.ts +78 -0
- package/src/interfaces/rest/routes/payments.ts +60 -0
- package/src/interfaces/rest/routes/pricing.ts +57 -0
- package/src/interfaces/rest/routes/promotions.ts +93 -0
- package/src/interfaces/rest/routes/search.ts +71 -0
- package/src/interfaces/rest/routes/webhooks.ts +46 -0
- package/src/interfaces/rest/schemas/admin-jobs.ts +40 -0
- package/src/interfaces/rest/schemas/audit.ts +46 -0
- package/src/interfaces/rest/schemas/carts.ts +125 -0
- package/src/interfaces/rest/schemas/catalog.ts +450 -0
- package/src/interfaces/rest/schemas/checkout.ts +66 -0
- package/src/interfaces/rest/schemas/customer-portal.ts +195 -0
- package/src/interfaces/rest/schemas/inventory.ts +138 -0
- package/src/interfaces/rest/schemas/media.ts +75 -0
- package/src/interfaces/rest/schemas/orders.ts +104 -0
- package/src/interfaces/rest/schemas/pricing.ts +80 -0
- package/src/interfaces/rest/schemas/promotions.ts +110 -0
- package/src/interfaces/rest/schemas/responses.ts +85 -0
- package/src/interfaces/rest/schemas/search.ts +58 -0
- package/src/interfaces/rest/schemas/shared.ts +62 -0
- package/src/interfaces/rest/schemas/webhooks.ts +68 -0
- package/src/interfaces/rest/utils.ts +104 -0
- package/src/interfaces/rest/webhook-router.ts +50 -0
- package/src/kernel/compensation/executor.ts +61 -0
- package/src/kernel/compensation/types.ts +26 -0
- package/src/kernel/database/adapter.ts +21 -0
- package/src/kernel/database/drizzle-db.ts +56 -0
- package/src/kernel/database/migrate.ts +76 -0
- package/src/kernel/database/plugin-types.ts +34 -0
- package/src/kernel/database/schema.ts +49 -0
- package/src/kernel/database/scoped-db.ts +68 -0
- package/src/kernel/database/tx-context.ts +46 -0
- package/src/kernel/error-mapper.ts +15 -0
- package/src/kernel/errors.ts +89 -0
- package/src/kernel/factory/repository-factory.ts +244 -0
- package/src/kernel/hooks/create-context.ts +43 -0
- package/src/kernel/hooks/executor.ts +88 -0
- package/src/kernel/hooks/registry.ts +74 -0
- package/src/kernel/hooks/types.ts +52 -0
- package/src/kernel/http-error.ts +44 -0
- package/src/kernel/jobs/adapter.ts +36 -0
- package/src/kernel/jobs/drizzle-adapter.ts +58 -0
- package/src/kernel/jobs/runner.ts +153 -0
- package/src/kernel/jobs/schema.ts +46 -0
- package/src/kernel/jobs/types.ts +30 -0
- package/src/kernel/local-api.ts +187 -0
- package/src/kernel/plugin/manifest.ts +271 -0
- package/src/kernel/query/executor.ts +184 -0
- package/src/kernel/query/registry.ts +46 -0
- package/src/kernel/result.ts +33 -0
- package/src/kernel/schema/extra-columns.ts +37 -0
- package/src/kernel/service-registry.ts +76 -0
- package/src/kernel/service-timing.ts +89 -0
- package/src/kernel/state-machine/machine.ts +101 -0
- package/src/modules/analytics/drizzle-adapter.ts +426 -0
- package/src/modules/analytics/hooks.ts +11 -0
- package/src/modules/analytics/models.ts +125 -0
- package/src/modules/analytics/repository/index.ts +6 -0
- package/src/modules/analytics/service.ts +245 -0
- package/src/modules/analytics/types.ts +180 -0
- package/src/modules/audit/hooks.ts +78 -0
- package/src/modules/audit/schema.ts +33 -0
- package/src/modules/audit/service.ts +151 -0
- package/src/modules/cart/access.ts +27 -0
- package/src/modules/cart/matcher.ts +26 -0
- package/src/modules/cart/repository/index.ts +234 -0
- package/src/modules/cart/schema.ts +42 -0
- package/src/modules/cart/schemas.ts +38 -0
- package/src/modules/cart/service.ts +541 -0
- package/src/modules/catalog/repository/index.ts +772 -0
- package/src/modules/catalog/schema.ts +203 -0
- package/src/modules/catalog/schemas.ts +104 -0
- package/src/modules/catalog/service.ts +1544 -0
- package/src/modules/customers/repository/index.ts +327 -0
- package/src/modules/customers/schema.ts +64 -0
- package/src/modules/customers/service.ts +171 -0
- package/src/modules/fulfillment/repository/index.ts +426 -0
- package/src/modules/fulfillment/schema.ts +101 -0
- package/src/modules/fulfillment/service.ts +555 -0
- package/src/modules/fulfillment/types.ts +59 -0
- package/src/modules/inventory/repository/index.ts +509 -0
- package/src/modules/inventory/schema.ts +94 -0
- package/src/modules/inventory/schemas.ts +38 -0
- package/src/modules/inventory/service.ts +490 -0
- package/src/modules/media/adapter.ts +17 -0
- package/src/modules/media/repository/index.ts +274 -0
- package/src/modules/media/schema.ts +41 -0
- package/src/modules/media/service.ts +151 -0
- package/src/modules/orders/repository/index.ts +287 -0
- package/src/modules/orders/schema.ts +66 -0
- package/src/modules/orders/service.ts +619 -0
- package/src/modules/orders/stale-order-cleanup.ts +76 -0
- package/src/modules/organization/service.ts +191 -0
- package/src/modules/payments/adapter.ts +47 -0
- package/src/modules/payments/repository/index.ts +6 -0
- package/src/modules/payments/service.ts +107 -0
- package/src/modules/pricing/repository/index.ts +291 -0
- package/src/modules/pricing/schema.ts +71 -0
- package/src/modules/pricing/schemas.ts +38 -0
- package/src/modules/pricing/service.ts +494 -0
- package/src/modules/promotions/repository/index.ts +325 -0
- package/src/modules/promotions/schema.ts +62 -0
- package/src/modules/promotions/schemas.ts +38 -0
- package/src/modules/promotions/service.ts +598 -0
- package/src/modules/search/adapter.ts +57 -0
- package/src/modules/search/hooks.ts +12 -0
- package/src/modules/search/repository/index.ts +6 -0
- package/src/modules/search/service.ts +315 -0
- package/src/modules/shipping/calculator.ts +188 -0
- package/src/modules/shipping/repository/index.ts +6 -0
- package/src/modules/shipping/service.ts +51 -0
- package/src/modules/tax/adapter.ts +60 -0
- package/src/modules/tax/repository/index.ts +6 -0
- package/src/modules/tax/service.ts +53 -0
- package/src/modules/webhooks/hook.ts +34 -0
- package/src/modules/webhooks/repository/index.ts +278 -0
- package/src/modules/webhooks/schema.ts +56 -0
- package/src/modules/webhooks/service.ts +117 -0
- package/src/modules/webhooks/signing.ts +6 -0
- package/src/modules/webhooks/ssrf-guard.ts +71 -0
- package/src/modules/webhooks/tasks.ts +52 -0
- package/src/modules/webhooks/worker.ts +134 -0
- package/src/runtime/commerce.ts +145 -0
- package/src/runtime/kernel.ts +426 -0
- package/src/runtime/logger.ts +36 -0
- package/src/runtime/server.ts +355 -0
- package/src/runtime/shutdown.ts +43 -0
- package/src/test-utils/create-pglite-adapter.ts +129 -0
- package/src/test-utils/create-plugin-test-app.ts +128 -0
- package/src/test-utils/create-repository-test-harness.ts +16 -0
- package/src/test-utils/create-test-config.ts +190 -0
- package/src/test-utils/create-test-kernel.ts +7 -0
- package/src/test-utils/create-test-plugin-context.ts +75 -0
- package/src/test-utils/rest-api-test-utils.ts +265 -0
- package/src/test-utils/test-actors.ts +62 -0
- package/src/test-utils/typed-hooks.ts +54 -0
- package/src/types/commerce-types.ts +34 -0
- package/src/utils/id.ts +3 -0
- package/src/utils/logger.ts +18 -0
- 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
|
+
}
|