@porulle/core 0.1.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +17 -1
- package/dist/config/define-config.d.ts.map +1 -1
- package/dist/config/define-config.js +6 -0
- package/dist/config/types.d.ts +12 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/hooks/checkout.d.ts +3 -3
- package/dist/hooks/checkout.js +4 -4
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/interfaces/rest/audit-middleware.d.ts +46 -0
- package/dist/interfaces/rest/audit-middleware.d.ts.map +1 -0
- package/dist/interfaces/rest/audit-middleware.js +82 -0
- package/dist/interfaces/rest/parse-json.d.ts +39 -0
- package/dist/interfaces/rest/parse-json.d.ts.map +1 -0
- package/dist/interfaces/rest/parse-json.js +45 -0
- package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/catalog.js +17 -2
- package/dist/interfaces/rest/routes/customers.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/customers.js +59 -4
- package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/inventory.js +4 -2
- package/dist/interfaces/rest/routes/media.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/media.js +6 -0
- package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/orders.js +23 -1
- package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/promotions.js +21 -1
- package/dist/interfaces/rest/schemas/carts.d.ts +175 -0
- package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.d.ts +1410 -0
- package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.js +28 -1
- package/dist/interfaces/rest/schemas/checkout.d.ts +35 -0
- package/dist/interfaces/rest/schemas/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/customer-portal.d.ts +280 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/customers.d.ts +863 -0
- package/dist/interfaces/rest/schemas/customers.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/customers.js +88 -0
- package/dist/interfaces/rest/schemas/inventory.d.ts +182 -1
- package/dist/interfaces/rest/schemas/inventory.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/media.d.ts +105 -0
- package/dist/interfaces/rest/schemas/media.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/orders.d.ts +136 -0
- package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/orders.js +20 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts +70 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/promotions.d.ts +298 -0
- package/dist/interfaces/rest/schemas/promotions.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/promotions.js +25 -1
- package/dist/interfaces/rest/schemas/shared.d.ts +47 -0
- package/dist/interfaces/rest/schemas/shared.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/shared.js +8 -0
- package/dist/interfaces/rest/schemas/webhooks.d.ts +70 -0
- package/dist/interfaces/rest/schemas/webhooks.d.ts.map +1 -1
- package/dist/kernel/database/adapter.d.ts +6 -0
- package/dist/kernel/database/adapter.d.ts.map +1 -1
- package/dist/kernel/database/adapter.js +62 -1
- package/dist/kernel/database/migrate.d.ts +22 -2
- package/dist/kernel/database/migrate.d.ts.map +1 -1
- package/dist/kernel/database/migrate.js +42 -2
- package/dist/modules/analytics/hooks.d.ts +5 -5
- package/dist/modules/analytics/hooks.js +5 -5
- package/dist/modules/analytics/repository/index.d.ts.map +1 -1
- package/dist/modules/catalog/category-service.d.ts +6 -1
- package/dist/modules/catalog/category-service.d.ts.map +1 -1
- package/dist/modules/catalog/category-service.js +35 -5
- package/dist/modules/catalog/schema.d.ts +17 -0
- package/dist/modules/catalog/schema.d.ts.map +1 -1
- package/dist/modules/catalog/schema.js +2 -0
- package/dist/modules/catalog/service.d.ts +11 -2
- package/dist/modules/catalog/service.d.ts.map +1 -1
- package/dist/modules/catalog/service.js +8 -2
- package/dist/modules/customers/repository/index.d.ts +8 -1
- package/dist/modules/customers/repository/index.d.ts.map +1 -1
- package/dist/modules/customers/repository/index.js +42 -2
- package/dist/modules/customers/schema.d.ts +162 -0
- package/dist/modules/customers/schema.d.ts.map +1 -1
- package/dist/modules/customers/schema.js +18 -0
- package/dist/modules/customers/service.d.ts +32 -2
- package/dist/modules/customers/service.d.ts.map +1 -1
- package/dist/modules/customers/service.js +119 -3
- package/dist/modules/inventory/schemas.d.ts +7 -1
- package/dist/modules/inventory/schemas.d.ts.map +1 -1
- package/dist/modules/inventory/schemas.js +6 -1
- package/dist/modules/inventory/service.d.ts +18 -0
- package/dist/modules/inventory/service.d.ts.map +1 -1
- package/dist/modules/inventory/service.js +58 -21
- package/dist/modules/media/noop-adapter.d.ts +11 -0
- package/dist/modules/media/noop-adapter.d.ts.map +1 -0
- package/dist/modules/media/noop-adapter.js +31 -0
- package/dist/modules/orders/repository/index.d.ts +19 -0
- package/dist/modules/orders/repository/index.d.ts.map +1 -1
- package/dist/modules/orders/repository/index.js +42 -1
- package/dist/modules/orders/service.d.ts +31 -0
- package/dist/modules/orders/service.d.ts.map +1 -1
- package/dist/modules/orders/service.js +52 -2
- package/dist/modules/payments/repository/index.d.ts.map +1 -1
- package/dist/modules/promotions/schemas.d.ts +46 -0
- package/dist/modules/promotions/schemas.d.ts.map +1 -1
- package/dist/modules/promotions/schemas.js +19 -8
- package/dist/modules/promotions/service.d.ts +8 -2
- package/dist/modules/promotions/service.d.ts.map +1 -1
- package/dist/modules/promotions/service.js +74 -9
- package/dist/modules/search/repository/index.d.ts.map +1 -1
- package/dist/modules/shipping/repository/index.d.ts.map +1 -1
- package/dist/modules/tax/repository/index.d.ts.map +1 -1
- package/dist/runtime/client-ip.d.ts +31 -0
- package/dist/runtime/client-ip.d.ts.map +1 -0
- package/dist/runtime/client-ip.js +31 -0
- package/dist/runtime/server.d.ts +4 -0
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +35 -19
- package/dist/test-utils/create-plugin-test-app.d.ts.map +1 -1
- package/dist/test-utils/create-plugin-test-app.js +4 -1
- package/dist/test-utils/test-actors.d.ts +3 -2
- package/dist/test-utils/test-actors.d.ts.map +1 -1
- package/dist/test-utils/test-actors.js +3 -2
- package/package.json +4 -4
- package/src/auth/middleware.ts +18 -1
- package/src/config/define-config.ts +7 -0
- package/src/config/types.ts +12 -0
- package/src/hooks/checkout.ts +4 -4
- package/src/index.ts +18 -1
- package/src/interfaces/rest/audit-middleware.ts +110 -0
- package/src/interfaces/rest/parse-json.ts +81 -0
- package/src/interfaces/rest/routes/catalog.ts +21 -1
- package/src/interfaces/rest/routes/customers.ts +64 -3
- package/src/interfaces/rest/routes/inventory.ts +4 -2
- package/src/interfaces/rest/routes/media.ts +7 -0
- package/src/interfaces/rest/routes/orders.ts +21 -1
- package/src/interfaces/rest/routes/promotions.ts +22 -1
- package/src/interfaces/rest/schemas/catalog.ts +30 -1
- package/src/interfaces/rest/schemas/customers.ts +99 -0
- package/src/interfaces/rest/schemas/orders.ts +21 -0
- package/src/interfaces/rest/schemas/promotions.ts +32 -1
- package/src/interfaces/rest/schemas/shared.ts +9 -0
- package/src/kernel/database/adapter.ts +64 -1
- package/src/kernel/database/migrate.ts +50 -2
- package/src/modules/analytics/hooks.ts +5 -5
- package/src/modules/analytics/repository/index.ts +0 -1
- package/src/modules/catalog/category-service.ts +26 -5
- package/src/modules/catalog/schema.ts +2 -0
- package/src/modules/catalog/service.ts +25 -3
- package/src/modules/customers/repository/index.ts +55 -1
- package/src/modules/customers/schema.ts +19 -0
- package/src/modules/customers/service.ts +137 -2
- package/src/modules/inventory/schemas.ts +6 -1
- package/src/modules/inventory/service.ts +84 -29
- package/src/modules/media/noop-adapter.ts +36 -0
- package/src/modules/orders/repository/index.ts +63 -1
- package/src/modules/orders/service.ts +86 -3
- package/src/modules/payments/repository/index.ts +0 -1
- package/src/modules/promotions/schemas.ts +21 -4
- package/src/modules/promotions/service.ts +90 -18
- package/src/modules/search/repository/index.ts +0 -1
- package/src/modules/shipping/repository/index.ts +0 -1
- package/src/modules/tax/repository/index.ts +0 -1
- package/src/runtime/client-ip.ts +49 -0
- package/src/runtime/server.ts +39 -18
- package/src/test-utils/create-plugin-test-app.ts +3 -1
- package/src/test-utils/test-actors.ts +3 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A single field-level validation failure. Shape is stable across the API
|
|
6
|
+
* so clients can bind form errors without ad-hoc parsing.
|
|
7
|
+
*/
|
|
8
|
+
export interface ValidationIssue {
|
|
9
|
+
/** Dotted path to the offending field (e.g. "address.zip"). Empty for whole-body errors. */
|
|
10
|
+
path: string;
|
|
11
|
+
/** Human-readable message. */
|
|
12
|
+
message: string;
|
|
13
|
+
/** Machine code (zod issue code, or "invalid_json"). */
|
|
14
|
+
code: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The error envelope's optional `details` payload. */
|
|
18
|
+
export interface ErrorDetails {
|
|
19
|
+
issues?: ValidationIssue[];
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build the uniform error envelope `{ error: { code, message, details? } }`.
|
|
25
|
+
* `details` is omitted when not provided so existing responses are unchanged.
|
|
26
|
+
*/
|
|
27
|
+
export function err(
|
|
28
|
+
c: Context,
|
|
29
|
+
status: 400 | 401 | 403 | 404 | 409 | 422 | 500,
|
|
30
|
+
code: string,
|
|
31
|
+
message: string,
|
|
32
|
+
details?: ErrorDetails,
|
|
33
|
+
): Response {
|
|
34
|
+
return c.json(
|
|
35
|
+
{ error: { code, message, ...(details ? { details } : {}) } },
|
|
36
|
+
status,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Parse and validate a request body against a Zod schema.
|
|
42
|
+
*
|
|
43
|
+
* Returns the typed value on success, or a 422 `Response` (the uniform error
|
|
44
|
+
* envelope, with `details.issues[]` populated from the Zod failure) that the
|
|
45
|
+
* caller returns as-is. Replaces the unsafe `(await c.req.json()) as T` cast.
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* const body = await parseJson(c, MyBodySchema);
|
|
49
|
+
* if (body instanceof Response) return body;
|
|
50
|
+
* // body is z.infer<typeof MyBodySchema>
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export async function parseJson<S extends z.ZodType>(
|
|
54
|
+
c: Context,
|
|
55
|
+
schema: S,
|
|
56
|
+
): Promise<z.infer<S> | Response> {
|
|
57
|
+
let raw: unknown;
|
|
58
|
+
try {
|
|
59
|
+
raw = await c.req.json();
|
|
60
|
+
} catch {
|
|
61
|
+
return err(c, 422, "VALIDATION_FAILED", "Request body must be valid JSON.", {
|
|
62
|
+
issues: [{ path: "", message: "Request body must be valid JSON.", code: "invalid_json" }],
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const result = schema.safeParse(raw);
|
|
67
|
+
if (!result.success) {
|
|
68
|
+
const issues: ValidationIssue[] = result.error.issues.map((issue) => ({
|
|
69
|
+
path: issue.path.map(String).join("."),
|
|
70
|
+
message: issue.message,
|
|
71
|
+
code: issue.code,
|
|
72
|
+
}));
|
|
73
|
+
const first = issues[0];
|
|
74
|
+
const message = first
|
|
75
|
+
? `${first.path ? `${first.path}: ` : ""}${first.message}`
|
|
76
|
+
: "Validation failed.";
|
|
77
|
+
return err(c, 422, "VALIDATION_FAILED", message, { issues });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return result.data;
|
|
81
|
+
}
|
|
@@ -35,6 +35,8 @@ import {
|
|
|
35
35
|
updateEntityRoute,
|
|
36
36
|
setEntityAttributesRoute,
|
|
37
37
|
createCategoryRoute,
|
|
38
|
+
archiveCategoryRoute,
|
|
39
|
+
restoreCategoryRoute,
|
|
38
40
|
updateCategoryRoute,
|
|
39
41
|
createBrandRoute,
|
|
40
42
|
updateBrandRoute,
|
|
@@ -280,7 +282,11 @@ export function catalogRoutes(kernel: Kernel) {
|
|
|
280
282
|
|
|
281
283
|
// @ts-expect-error -- openapi handler union return type
|
|
282
284
|
router.openapi(listCategoriesRoute, async (c) => {
|
|
283
|
-
const
|
|
285
|
+
const includeArchived = c.req.query("includeArchived") === "true";
|
|
286
|
+
const result = await kernel.services.catalog.listCategories(
|
|
287
|
+
{ actor: c.get("actor"), tx: null, requestId: "" },
|
|
288
|
+
{ includeArchived },
|
|
289
|
+
);
|
|
284
290
|
if (!result.ok)
|
|
285
291
|
return c.json(
|
|
286
292
|
mapErrorToResponse(result.error),
|
|
@@ -289,6 +295,20 @@ export function catalogRoutes(kernel: Kernel) {
|
|
|
289
295
|
return c.json({ data: result.value });
|
|
290
296
|
});
|
|
291
297
|
|
|
298
|
+
// @ts-expect-error -- openapi handler union return type
|
|
299
|
+
router.openapi(archiveCategoryRoute, async (c) => {
|
|
300
|
+
const result = await kernel.services.catalog.archiveCategory(c.req.param("categoryId"), c.get("actor"));
|
|
301
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
302
|
+
return c.json({ data: result.value });
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// @ts-expect-error -- openapi handler union return type
|
|
306
|
+
router.openapi(restoreCategoryRoute, async (c) => {
|
|
307
|
+
const result = await kernel.services.catalog.restoreCategory(c.req.param("categoryId"), c.get("actor"));
|
|
308
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
309
|
+
return c.json({ data: result.value });
|
|
310
|
+
});
|
|
311
|
+
|
|
292
312
|
// @ts-expect-error -- openapi handler union return type
|
|
293
313
|
router.openapi(createCategoryRoute, async (c) => {
|
|
294
314
|
const result = await kernel.services.catalog.createCategory(
|
|
@@ -2,12 +2,17 @@ import { OpenAPIHono } from "@hono/zod-openapi";
|
|
|
2
2
|
import type { Kernel } from "../../../runtime/kernel.js";
|
|
3
3
|
import {
|
|
4
4
|
listCustomersRoute,
|
|
5
|
+
createCustomerRoute,
|
|
5
6
|
getCustomerRoute,
|
|
6
7
|
updateCustomerRoute,
|
|
7
8
|
getCustomerOrdersRoute,
|
|
8
9
|
getCustomerAddressesRoute,
|
|
10
|
+
listInteractionsRoute,
|
|
11
|
+
createInteractionRoute,
|
|
12
|
+
updateInteractionRoute,
|
|
13
|
+
deleteInteractionRoute,
|
|
9
14
|
} from "../schemas/customers.js";
|
|
10
|
-
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, parsePagination, requirePerm } from "../utils.js";
|
|
15
|
+
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, parsePagination, parseInclude, requirePerm } from "../utils.js";
|
|
11
16
|
|
|
12
17
|
export function customerRoutes(kernel: Kernel) {
|
|
13
18
|
const router = new OpenAPIHono<AppEnv>();
|
|
@@ -37,6 +42,14 @@ export function customerRoutes(kernel: Kernel) {
|
|
|
37
42
|
});
|
|
38
43
|
});
|
|
39
44
|
|
|
45
|
+
// @ts-expect-error -- openapi handler union return type
|
|
46
|
+
router.openapi(createCustomerRoute, async (c) => {
|
|
47
|
+
const body = c.req.valid("json");
|
|
48
|
+
const result = await kernel.services.customers.createWalkIn(body, c.get("actor"));
|
|
49
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
50
|
+
return c.json({ data: result.value }, 201);
|
|
51
|
+
});
|
|
52
|
+
|
|
40
53
|
// @ts-expect-error -- openapi handler union return type
|
|
41
54
|
router.openapi(getCustomerRoute, async (c) => {
|
|
42
55
|
const { id } = c.req.valid("param");
|
|
@@ -58,7 +71,10 @@ export function customerRoutes(kernel: Kernel) {
|
|
|
58
71
|
for (const [k, v] of Object.entries(body)) {
|
|
59
72
|
if (v !== undefined) updates[k] = v;
|
|
60
73
|
}
|
|
61
|
-
const
|
|
74
|
+
const replaceMetadata = c.req.query("metadataReplace") === "true";
|
|
75
|
+
const result = await kernel.services.customers.update(id, updates, actor, undefined, {
|
|
76
|
+
replaceMetadata,
|
|
77
|
+
});
|
|
62
78
|
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
63
79
|
return c.json({ data: result.value });
|
|
64
80
|
});
|
|
@@ -69,13 +85,22 @@ export function customerRoutes(kernel: Kernel) {
|
|
|
69
85
|
const actor = c.get("actor");
|
|
70
86
|
const { page, limit } = parsePagination(c.req.query());
|
|
71
87
|
const status = c.req.query("status") || undefined;
|
|
88
|
+
const includeTotals = parseInclude(c.req.query("include")).has("totals");
|
|
72
89
|
|
|
73
90
|
const result = await kernel.services.orders.listByCustomer(
|
|
74
91
|
id,
|
|
75
|
-
{ page, limit, ...(status ? { status } : {}) },
|
|
92
|
+
{ page, limit, ...(status ? { status } : {}), ...(includeTotals ? { includeTotals: true } : {}) },
|
|
76
93
|
actor,
|
|
77
94
|
);
|
|
78
95
|
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
96
|
+
if (includeTotals) {
|
|
97
|
+
// Wrapped shape: { data: { items, totals } } (+ pagination meta).
|
|
98
|
+
return c.json({
|
|
99
|
+
data: { items: result.value.items, totals: result.value.totals },
|
|
100
|
+
meta: { pagination: result.value.pagination },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// Default (back-compat): flat array.
|
|
79
104
|
return c.json({ data: result.value.items, meta: { pagination: result.value.pagination } });
|
|
80
105
|
});
|
|
81
106
|
|
|
@@ -96,5 +121,41 @@ export function customerRoutes(kernel: Kernel) {
|
|
|
96
121
|
return c.json({ data: addressResult.value });
|
|
97
122
|
});
|
|
98
123
|
|
|
124
|
+
// ─── Customer interactions (#3) ──────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
// @ts-expect-error -- openapi handler union return type
|
|
127
|
+
router.openapi(listInteractionsRoute, async (c) => {
|
|
128
|
+
const { id } = c.req.valid("param");
|
|
129
|
+
const result = await kernel.services.customers.listInteractions(id, c.get("actor"));
|
|
130
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
131
|
+
return c.json({ data: result.value });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// @ts-expect-error -- openapi handler union return type
|
|
135
|
+
router.openapi(createInteractionRoute, async (c) => {
|
|
136
|
+
const { id } = c.req.valid("param");
|
|
137
|
+
const body = c.req.valid("json");
|
|
138
|
+
const result = await kernel.services.customers.createInteraction(id, body, c.get("actor"));
|
|
139
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
140
|
+
return c.json({ data: result.value }, 201);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// @ts-expect-error -- openapi handler union return type
|
|
144
|
+
router.openapi(updateInteractionRoute, async (c) => {
|
|
145
|
+
const { id, iid } = c.req.valid("param");
|
|
146
|
+
const body = c.req.valid("json");
|
|
147
|
+
const result = await kernel.services.customers.updateInteraction(id, iid, body, c.get("actor"));
|
|
148
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
149
|
+
return c.json({ data: result.value });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// @ts-expect-error -- openapi handler union return type
|
|
153
|
+
router.openapi(deleteInteractionRoute, async (c) => {
|
|
154
|
+
const { id, iid } = c.req.valid("param");
|
|
155
|
+
const result = await kernel.services.customers.deleteInteraction(id, iid, c.get("actor"));
|
|
156
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
157
|
+
return c.json({ data: { deleted: true } });
|
|
158
|
+
});
|
|
159
|
+
|
|
99
160
|
return router;
|
|
100
161
|
}
|
|
@@ -51,9 +51,11 @@ export function inventoryRoutes(kernel: Kernel) {
|
|
|
51
51
|
// @ts-expect-error -- openapi handler union return type
|
|
52
52
|
router.openapi(inventoryAdjustRoute, async (c) => {
|
|
53
53
|
const body = c.req.valid("json");
|
|
54
|
-
const result = await kernel.services.inventory.
|
|
54
|
+
const result = await kernel.services.inventory.adjustDetailed(body, c.get("actor"));
|
|
55
55
|
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
56
|
-
|
|
56
|
+
const { level, before, after, delta, movementId } = result.value;
|
|
57
|
+
// Additive: level fields (back-compat) plus before/after/delta/movementId.
|
|
58
|
+
return c.json({ data: { ...level, before, after, delta, movementId } });
|
|
57
59
|
});
|
|
58
60
|
|
|
59
61
|
// @ts-expect-error -- openapi handler union return type
|
|
@@ -11,6 +11,13 @@ export function mediaRoutes(kernel: Kernel) {
|
|
|
11
11
|
router.use("/upload", requirePerm("media:write"));
|
|
12
12
|
|
|
13
13
|
router.post("/upload", async (c) => {
|
|
14
|
+
// No storage configured (the default no-op adapter) → media is disabled.
|
|
15
|
+
if (kernel.config.storage?.providerId === "noop") {
|
|
16
|
+
return c.json({
|
|
17
|
+
error: { code: "storage_not_supported", message: "Media storage is not configured." },
|
|
18
|
+
}, 501);
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
const body = await c.req.parseBody();
|
|
15
22
|
const file = body.file as File;
|
|
16
23
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
2
|
import type { Kernel } from "../../../runtime/kernel.js";
|
|
3
|
-
import { changeOrderStatusRoute, listOrdersRoute, getOrderRoute, getOrderFulfillmentsRoute } from "../schemas/orders.js";
|
|
3
|
+
import { changeOrderStatusRoute, listOrdersRoute, orderLookupRoute, getOrderRoute, getOrderFulfillmentsRoute } from "../schemas/orders.js";
|
|
4
4
|
import { type AppEnv, isUUID, mapErrorToResponse, mapErrorToStatus, parsePagination } from "../utils.js";
|
|
5
5
|
|
|
6
6
|
export function orderRoutes(kernel: Kernel) {
|
|
@@ -28,6 +28,26 @@ export function orderRoutes(kernel: Kernel) {
|
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
// Registered before getOrderRoute so "/lookup" isn't matched as an idOrNumber.
|
|
32
|
+
// @ts-expect-error -- openapi handler union return type
|
|
33
|
+
router.openapi(orderLookupRoute, async (c) => {
|
|
34
|
+
const q = c.req.query("q") ?? "";
|
|
35
|
+
const fromRaw = c.req.query("from");
|
|
36
|
+
const toRaw = c.req.query("to");
|
|
37
|
+
const opts: { from?: Date; to?: Date } = {};
|
|
38
|
+
if (fromRaw) {
|
|
39
|
+
const d = new Date(fromRaw);
|
|
40
|
+
if (!Number.isNaN(d.getTime())) opts.from = d;
|
|
41
|
+
}
|
|
42
|
+
if (toRaw) {
|
|
43
|
+
const d = new Date(toRaw);
|
|
44
|
+
if (!Number.isNaN(d.getTime())) opts.to = d;
|
|
45
|
+
}
|
|
46
|
+
const result = await kernel.services.orders.lookup(q, opts, c.get("actor"));
|
|
47
|
+
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
48
|
+
return c.json({ data: result.value });
|
|
49
|
+
});
|
|
50
|
+
|
|
31
51
|
// @ts-expect-error -- openapi handler union return type
|
|
32
52
|
router.openapi(getOrderRoute, async (c) => {
|
|
33
53
|
const idOrNumber = c.req.param("idOrNumber");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
2
|
import { rateLimiter } from "hono-rate-limiter";
|
|
3
3
|
import type { Kernel } from "../../../runtime/kernel.js";
|
|
4
|
-
import { createPromotionRoute, validatePromotionRoute, deactivatePromotionRoute, listPromotionsRoute } from "../schemas/promotions.js";
|
|
4
|
+
import { createPromotionRoute, updatePromotionRoute, validatePromotionRoute, deactivatePromotionRoute, listPromotionsRoute } from "../schemas/promotions.js";
|
|
5
5
|
import type { PromotionStatusFilter } from "../../../modules/promotions/service.js";
|
|
6
6
|
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, requirePerm } from "../utils.js";
|
|
7
7
|
import { resolveOrgId } from "../../../auth/org.js";
|
|
@@ -77,6 +77,27 @@ export function promotionRoutes(kernel: Kernel) {
|
|
|
77
77
|
return c.json({ data: result.value });
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
// Guard inline (not via router.use("/:id")) because a "/:id" middleware
|
|
81
|
+
// would also match single-segment routes like POST /validate.
|
|
82
|
+
router.use("/:id", async (c, next) => {
|
|
83
|
+
if (c.req.method !== "PATCH") return next();
|
|
84
|
+
return requirePerm("promotions:manage")(c, next);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// @ts-expect-error -- openapi() enforces strict response typing but our handler
|
|
88
|
+
// returns union responses (200 | 400 | 404 | 422). The route definition
|
|
89
|
+
// documents the contract; the handler returns dynamic status.
|
|
90
|
+
router.openapi(updatePromotionRoute, async (c) => {
|
|
91
|
+
const body = c.req.valid("json");
|
|
92
|
+
const actor = c.get("actor");
|
|
93
|
+
const orgId = resolveOrgId(actor);
|
|
94
|
+
const result = await kernel.services.promotions.update(orgId, c.req.param("id"), body, actor);
|
|
95
|
+
if (!result.ok) {
|
|
96
|
+
return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
97
|
+
}
|
|
98
|
+
return c.json({ data: result.value });
|
|
99
|
+
});
|
|
100
|
+
|
|
80
101
|
router.use("/:id/deactivate", requirePerm("promotions:manage"));
|
|
81
102
|
|
|
82
103
|
// @ts-expect-error -- openapi() enforces strict response typing but our handler
|
|
@@ -108,7 +108,12 @@ export const listCategoriesRoute = createRoute({
|
|
|
108
108
|
method: "get",
|
|
109
109
|
path: "/categories",
|
|
110
110
|
tags: ["Catalog"],
|
|
111
|
-
summary: "List
|
|
111
|
+
summary: "List categories (active by default)",
|
|
112
|
+
request: {
|
|
113
|
+
query: z.object({
|
|
114
|
+
includeArchived: z.string().optional().openapi({ example: "true" }),
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
112
117
|
responses: {
|
|
113
118
|
200: { content: { "application/json": { schema: DataResponse } }, description: "Success" },
|
|
114
119
|
},
|
|
@@ -342,6 +347,30 @@ export const updateCategoryRoute = createRoute({
|
|
|
342
347
|
},
|
|
343
348
|
});
|
|
344
349
|
|
|
350
|
+
export const archiveCategoryRoute = createRoute({
|
|
351
|
+
method: "post",
|
|
352
|
+
path: "/categories/{categoryId}/archive",
|
|
353
|
+
tags: ["Catalog"],
|
|
354
|
+
summary: "Archive a category (soft delete)",
|
|
355
|
+
request: { params: CategoryIdParam },
|
|
356
|
+
responses: {
|
|
357
|
+
200: { content: { "application/json": { schema: DataResponse } }, description: "Archived" },
|
|
358
|
+
...errorResponses,
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
export const restoreCategoryRoute = createRoute({
|
|
363
|
+
method: "post",
|
|
364
|
+
path: "/categories/{categoryId}/restore",
|
|
365
|
+
tags: ["Catalog"],
|
|
366
|
+
summary: "Restore an archived category",
|
|
367
|
+
request: { params: CategoryIdParam },
|
|
368
|
+
responses: {
|
|
369
|
+
200: { content: { "application/json": { schema: DataResponse } }, description: "Restored" },
|
|
370
|
+
...errorResponses,
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
|
|
345
374
|
export const createBrandRoute = createRoute({
|
|
346
375
|
method: "post",
|
|
347
376
|
path: "/brands",
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import { z, createRoute } from "@hono/zod-openapi";
|
|
2
2
|
import { errorResponses } from "./shared.js";
|
|
3
3
|
|
|
4
|
+
export const CreateCustomerBodySchema = z.object({
|
|
5
|
+
// Optional: omit for walk-in / POS customers who never log in. A synthetic
|
|
6
|
+
// anonymous_<uuid> id is generated and metadata.walkIn is set.
|
|
7
|
+
userId: z.string().optional().openapi({ example: "user_123" }),
|
|
8
|
+
firstName: z.string().optional().openapi({ example: "Nimali" }),
|
|
9
|
+
lastName: z.string().optional().openapi({ example: "Perera" }),
|
|
10
|
+
phone: z.string().optional().openapi({ example: "+94 77 412 6601" }),
|
|
11
|
+
email: z.string().optional().openapi({ example: "nimali@example.com" }),
|
|
12
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
13
|
+
}).openapi("CreateCustomerBody");
|
|
14
|
+
|
|
15
|
+
export const createCustomerRoute = createRoute({
|
|
16
|
+
method: "post",
|
|
17
|
+
path: "/",
|
|
18
|
+
tags: ["Customers"],
|
|
19
|
+
summary: "Create a customer (supports walk-in / userId-less)",
|
|
20
|
+
request: {
|
|
21
|
+
body: { content: { "application/json": { schema: CreateCustomerBodySchema } }, required: true },
|
|
22
|
+
},
|
|
23
|
+
responses: {
|
|
24
|
+
201: {
|
|
25
|
+
content: { "application/json": { schema: z.object({ data: z.record(z.string(), z.unknown()) }) } },
|
|
26
|
+
description: "Customer created.",
|
|
27
|
+
},
|
|
28
|
+
...errorResponses,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
4
32
|
export const listCustomersRoute = createRoute({
|
|
5
33
|
method: "get",
|
|
6
34
|
path: "/",
|
|
@@ -153,3 +181,74 @@ export const getCustomerAddressesRoute = createRoute({
|
|
|
153
181
|
...errorResponses,
|
|
154
182
|
},
|
|
155
183
|
});
|
|
184
|
+
|
|
185
|
+
// ─── Customer interactions (#3) ──────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
export const InteractionKindEnum = z.enum([
|
|
188
|
+
"visit", "call", "inquiry", "fitting", "follow_up", "message",
|
|
189
|
+
]);
|
|
190
|
+
|
|
191
|
+
export const CreateInteractionBodySchema = z.object({
|
|
192
|
+
kind: InteractionKindEnum.openapi({ example: "visit" }),
|
|
193
|
+
notes: z.string().min(1).openapi({ example: "Asked about the navy blazer in M." }),
|
|
194
|
+
relatedEntityId: z.string().uuid().nullable().optional(),
|
|
195
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
196
|
+
}).openapi("CreateInteractionBody");
|
|
197
|
+
|
|
198
|
+
export const UpdateInteractionBodySchema = CreateInteractionBodySchema.partial().openapi("UpdateInteractionBody");
|
|
199
|
+
|
|
200
|
+
const InteractionDataResponse = z.object({ data: z.record(z.string(), z.unknown()) });
|
|
201
|
+
|
|
202
|
+
export const listInteractionsRoute = createRoute({
|
|
203
|
+
method: "get",
|
|
204
|
+
path: "/{id}/interactions",
|
|
205
|
+
tags: ["Customers"],
|
|
206
|
+
summary: "List a customer's interactions",
|
|
207
|
+
request: { params: z.object({ id: z.string().uuid() }) },
|
|
208
|
+
responses: {
|
|
209
|
+
200: { content: { "application/json": { schema: z.object({ data: z.array(z.record(z.string(), z.unknown())) }) } }, description: "Interactions" },
|
|
210
|
+
...errorResponses,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
export const createInteractionRoute = createRoute({
|
|
215
|
+
method: "post",
|
|
216
|
+
path: "/{id}/interactions",
|
|
217
|
+
tags: ["Customers"],
|
|
218
|
+
summary: "Log a customer interaction",
|
|
219
|
+
request: {
|
|
220
|
+
params: z.object({ id: z.string().uuid() }),
|
|
221
|
+
body: { content: { "application/json": { schema: CreateInteractionBodySchema } }, required: true },
|
|
222
|
+
},
|
|
223
|
+
responses: {
|
|
224
|
+
201: { content: { "application/json": { schema: InteractionDataResponse } }, description: "Interaction logged" },
|
|
225
|
+
...errorResponses,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
export const updateInteractionRoute = createRoute({
|
|
230
|
+
method: "patch",
|
|
231
|
+
path: "/{id}/interactions/{iid}",
|
|
232
|
+
tags: ["Customers"],
|
|
233
|
+
summary: "Edit a customer interaction",
|
|
234
|
+
request: {
|
|
235
|
+
params: z.object({ id: z.string().uuid(), iid: z.string().uuid() }),
|
|
236
|
+
body: { content: { "application/json": { schema: UpdateInteractionBodySchema } }, required: true },
|
|
237
|
+
},
|
|
238
|
+
responses: {
|
|
239
|
+
200: { content: { "application/json": { schema: InteractionDataResponse } }, description: "Interaction updated" },
|
|
240
|
+
...errorResponses,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
export const deleteInteractionRoute = createRoute({
|
|
245
|
+
method: "delete",
|
|
246
|
+
path: "/{id}/interactions/{iid}",
|
|
247
|
+
tags: ["Customers"],
|
|
248
|
+
summary: "Delete a customer interaction",
|
|
249
|
+
request: { params: z.object({ id: z.string().uuid(), iid: z.string().uuid() }) },
|
|
250
|
+
responses: {
|
|
251
|
+
200: { content: { "application/json": { schema: z.object({ data: z.object({ deleted: z.literal(true) }) }) } }, description: "Deleted" },
|
|
252
|
+
...errorResponses,
|
|
253
|
+
},
|
|
254
|
+
});
|
|
@@ -44,6 +44,27 @@ export const listOrdersRoute = createRoute({
|
|
|
44
44
|
},
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
+
export const orderLookupRoute = createRoute({
|
|
48
|
+
method: "get",
|
|
49
|
+
path: "/lookup",
|
|
50
|
+
tags: ["Orders"],
|
|
51
|
+
summary: "Fuzzy order lookup (receipt-less return / support)",
|
|
52
|
+
description: "Find orders by order number, customer email/name/phone, or walk-in label. Minimum query length is 3 characters.",
|
|
53
|
+
request: {
|
|
54
|
+
query: z.object({
|
|
55
|
+
q: z.string().optional().openapi({ example: "Perera" }),
|
|
56
|
+
from: z.string().optional().openapi({ example: "2026-01-01" }),
|
|
57
|
+
to: z.string().optional().openapi({ example: "2026-12-31" }),
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
responses: {
|
|
61
|
+
200: {
|
|
62
|
+
content: { "application/json": { schema: z.object({ data: z.object({ items: z.array(z.record(z.string(), z.unknown())), hint: z.string().optional() }) }) } },
|
|
63
|
+
description: "Lookup results",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
47
68
|
export const getOrderRoute = createRoute({
|
|
48
69
|
method: "get",
|
|
49
70
|
path: "/{idOrNumber}",
|
|
@@ -3,9 +3,16 @@ import { ErrorSchema, errorResponses, UuidParamSchema } from "./shared.js";
|
|
|
3
3
|
|
|
4
4
|
// ─── Request Schemas ────────────────────────────────────────────────────────
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
CreatePromotionBodySchema,
|
|
8
|
+
UpdatePromotionBodySchema as UpdatePromotionBodySchemaBase,
|
|
9
|
+
} from "../../../modules/promotions/schemas.js";
|
|
7
10
|
export { CreatePromotionBodySchema };
|
|
8
11
|
|
|
12
|
+
export const UpdatePromotionBodySchema = UpdatePromotionBodySchemaBase.openapi(
|
|
13
|
+
"UpdatePromotionRequest",
|
|
14
|
+
);
|
|
15
|
+
|
|
9
16
|
export const ValidatePromotionBodySchema = z.object({
|
|
10
17
|
code: z.string().openapi({ example: "SUMMER10" }),
|
|
11
18
|
currency: z.string().length(3).openapi({ example: "USD" }),
|
|
@@ -98,6 +105,30 @@ export const validatePromotionRoute = createRoute({
|
|
|
98
105
|
},
|
|
99
106
|
});
|
|
100
107
|
|
|
108
|
+
export const updatePromotionRoute = createRoute({
|
|
109
|
+
method: "patch",
|
|
110
|
+
path: "/{id}",
|
|
111
|
+
tags: ["Promotions"],
|
|
112
|
+
summary: "Edit a promotion",
|
|
113
|
+
description: "Update any subset of a promotion's fields (name, code, type, value, validity dates, scope, metadata, isActive). Validated the same way create is.",
|
|
114
|
+
request: {
|
|
115
|
+
params: PromotionIdParam,
|
|
116
|
+
body: {
|
|
117
|
+
content: {
|
|
118
|
+
"application/json": { schema: UpdatePromotionBodySchema },
|
|
119
|
+
},
|
|
120
|
+
required: true,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
responses: {
|
|
124
|
+
200: {
|
|
125
|
+
content: { "application/json": { schema: PromotionResponseSchema } },
|
|
126
|
+
description: "Promotion updated.",
|
|
127
|
+
},
|
|
128
|
+
...errorResponses,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
101
132
|
export const deactivatePromotionRoute = createRoute({
|
|
102
133
|
method: "post",
|
|
103
134
|
path: "/{id}/deactivate",
|
|
@@ -2,10 +2,19 @@ import { z } from "@hono/zod-openapi";
|
|
|
2
2
|
|
|
3
3
|
// ─── Error Response ──────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
|
+
export const ValidationIssueSchema = z.object({
|
|
6
|
+
path: z.string().openapi({ example: "cartId" }),
|
|
7
|
+
message: z.string().openapi({ example: "Invalid uuid" }),
|
|
8
|
+
code: z.string().openapi({ example: "invalid_string" }),
|
|
9
|
+
}).openapi("ValidationIssue");
|
|
10
|
+
|
|
5
11
|
export const ErrorSchema = z.object({
|
|
6
12
|
error: z.object({
|
|
7
13
|
code: z.string().openapi({ example: "VALIDATION_FAILED" }),
|
|
8
14
|
message: z.string().openapi({ example: "cartId: Invalid uuid" }),
|
|
15
|
+
details: z.object({
|
|
16
|
+
issues: z.array(ValidationIssueSchema).optional(),
|
|
17
|
+
}).optional(),
|
|
9
18
|
}),
|
|
10
19
|
}).openapi("Error");
|
|
11
20
|
|
|
@@ -16,6 +16,69 @@ export interface DatabaseConnectionFactoryInput {
|
|
|
16
16
|
adapter: DatabaseAdapter;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Marker used to retrieve the raw, un-normalized driver from a wrapped db.
|
|
21
|
+
* Tools that drive the driver directly (drizzle-kit's `pushSchema`, which
|
|
22
|
+
* relies on the native `db.execute()` shape) must unwrap first via {@link unwrapDb}.
|
|
23
|
+
*/
|
|
24
|
+
const RAW_DB = Symbol.for("porulle.rawDb");
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Return the raw driver behind a db normalized by {@link normalizeExecuteShape}.
|
|
28
|
+
* If `db` isn't wrapped, it is returned unchanged. Use before handing a db to
|
|
29
|
+
* drizzle-kit (`pushSchema`/introspection), which needs the native result shape.
|
|
30
|
+
*/
|
|
31
|
+
export function unwrapDb<T>(db: T): T {
|
|
32
|
+
if (db != null && typeof db === "object") {
|
|
33
|
+
const raw = (db as Record<symbol, unknown>)[RAW_DB];
|
|
34
|
+
if (raw) return raw as T;
|
|
35
|
+
}
|
|
36
|
+
return db;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Normalize `db.execute()` so it returns a row array regardless of the
|
|
41
|
+
* underlying driver. postgres-js returns the rows directly, while neon-http
|
|
42
|
+
* and node-postgres (and PGlite) return `{ rows, command, rowCount }`.
|
|
43
|
+
* Without this, raw-SQL code that destructures the result as an array breaks
|
|
44
|
+
* on the `{ rows }` drivers. We intercept only `execute`; every other method
|
|
45
|
+
* is bound to the real driver instance so its internals are untouched.
|
|
46
|
+
*/
|
|
47
|
+
function normalizeExecuteShape<T extends object>(db: T): T {
|
|
48
|
+
return new Proxy(db, {
|
|
49
|
+
get(target, prop, receiver) {
|
|
50
|
+
if (prop === RAW_DB) return target;
|
|
51
|
+
if (prop === "execute") {
|
|
52
|
+
const orig = Reflect.get(target, prop, receiver);
|
|
53
|
+
if (typeof orig !== "function") return orig;
|
|
54
|
+
return async (...args: unknown[]) => {
|
|
55
|
+
const result = await (orig as (...a: unknown[]) => Promise<unknown>).apply(target, args);
|
|
56
|
+
if (
|
|
57
|
+
result != null &&
|
|
58
|
+
typeof result === "object" &&
|
|
59
|
+
!Array.isArray(result) &&
|
|
60
|
+
Array.isArray((result as { rows?: unknown }).rows)
|
|
61
|
+
) {
|
|
62
|
+
return (result as { rows: unknown[] }).rows;
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const value = Reflect.get(target, prop, target);
|
|
68
|
+
// Bind methods to the real instance so drizzle's internals (incl. any
|
|
69
|
+
// private fields) are never accessed through the proxy.
|
|
70
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
19
75
|
export function createDatabaseConnection(input: DatabaseConnectionFactoryInput): DatabaseAdapter {
|
|
20
|
-
|
|
76
|
+
const adapter = input.adapter;
|
|
77
|
+
return {
|
|
78
|
+
provider: adapter.provider,
|
|
79
|
+
db: normalizeExecuteShape(adapter.db as object),
|
|
80
|
+
transaction<T>(fn: (tx: unknown) => Promise<T>): Promise<T> {
|
|
81
|
+
return adapter.transaction((tx) => fn(normalizeExecuteShape(tx as object)));
|
|
82
|
+
},
|
|
83
|
+
};
|
|
21
84
|
}
|