@porulle/core 0.1.0 → 0.6.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/LICENSE +21 -0
- 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 +15 -15
- 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
|
@@ -4,6 +4,9 @@ import { createHookContext } from "../../kernel/hooks/create-context.js";
|
|
|
4
4
|
import { Err, Ok } from "../../kernel/result.js";
|
|
5
5
|
import { createLogger } from "../../utils/logger.js";
|
|
6
6
|
import { resolveOrgId } from "../../auth/org.js";
|
|
7
|
+
// PromotionType is derived from the zod enum (single source of truth) in
|
|
8
|
+
// ./schemas.js; re-export it here for backwards-compatible imports.
|
|
9
|
+
import { promotionTypeEnum } from "./schemas.js";
|
|
7
10
|
function hookContext(actor, services, database, tx) {
|
|
8
11
|
return createHookContext({
|
|
9
12
|
actor,
|
|
@@ -74,15 +77,8 @@ export class PromotionService {
|
|
|
74
77
|
if (input.value < 0) {
|
|
75
78
|
return Err(new CommerceValidationError("Promotion value cannot be negative."));
|
|
76
79
|
}
|
|
77
|
-
// Validate promotion type
|
|
78
|
-
const validTypes =
|
|
79
|
-
"percentage_off_order",
|
|
80
|
-
"fixed_off_order",
|
|
81
|
-
"percentage_off_item",
|
|
82
|
-
"fixed_off_item",
|
|
83
|
-
"free_shipping",
|
|
84
|
-
"buy_x_get_y",
|
|
85
|
-
];
|
|
80
|
+
// Validate promotion type against the single-source enum
|
|
81
|
+
const validTypes = promotionTypeEnum.options;
|
|
86
82
|
if (!validTypes.includes(input.type)) {
|
|
87
83
|
return Err(new CommerceValidationError(`Invalid promotion type "${input.type}". Must be one of: ${validTypes.join(", ")}`));
|
|
88
84
|
}
|
|
@@ -131,6 +127,75 @@ export class PromotionService {
|
|
|
131
127
|
await runAfterHooks(afterHooks, promotion, updated, "update", hctx);
|
|
132
128
|
return Ok(updated);
|
|
133
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Edit any subset of a promotion's fields. Validated the same way create is
|
|
132
|
+
* (value bounds, type enum, code uniqueness). Only provided keys are written.
|
|
133
|
+
*/
|
|
134
|
+
async update(orgId, id, input, actor, ctx) {
|
|
135
|
+
const existing = await this.repo.findById(orgId, id, ctx);
|
|
136
|
+
if (!existing) {
|
|
137
|
+
return Err(new CommerceNotFoundError("Promotion not found."));
|
|
138
|
+
}
|
|
139
|
+
if (input.value !== undefined && input.value < 0) {
|
|
140
|
+
return Err(new CommerceValidationError("Promotion value cannot be negative."));
|
|
141
|
+
}
|
|
142
|
+
if (input.type !== undefined) {
|
|
143
|
+
const validTypes = promotionTypeEnum.options;
|
|
144
|
+
if (!validTypes.includes(input.type)) {
|
|
145
|
+
return Err(new CommerceValidationError(`Invalid promotion type "${input.type}". Must be one of: ${validTypes.join(", ")}`));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
let normalizedCode;
|
|
149
|
+
if (input.code !== undefined) {
|
|
150
|
+
normalizedCode = input.code ? input.code.trim().toUpperCase() : null;
|
|
151
|
+
if (normalizedCode) {
|
|
152
|
+
const other = await this.repo.findByCode(orgId, normalizedCode, ctx);
|
|
153
|
+
if (other && other.id !== id) {
|
|
154
|
+
return Err(new CommerceValidationError(`Promotion code ${normalizedCode} already exists.`));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const patch = {};
|
|
159
|
+
if (input.name !== undefined)
|
|
160
|
+
patch.name = input.name;
|
|
161
|
+
if (input.type !== undefined)
|
|
162
|
+
patch.type = input.type;
|
|
163
|
+
if (input.value !== undefined)
|
|
164
|
+
patch.value = roundMoney(input.value);
|
|
165
|
+
if (input.isAutomatic !== undefined)
|
|
166
|
+
patch.isAutomatic = input.isAutomatic;
|
|
167
|
+
if (input.isActive !== undefined)
|
|
168
|
+
patch.isActive = input.isActive;
|
|
169
|
+
if (input.priority !== undefined)
|
|
170
|
+
patch.priority = input.priority;
|
|
171
|
+
if (input.conditions !== undefined)
|
|
172
|
+
patch.conditions = input.conditions;
|
|
173
|
+
if (input.metadata !== undefined)
|
|
174
|
+
patch.metadata = input.metadata;
|
|
175
|
+
if (normalizedCode !== undefined)
|
|
176
|
+
patch.code = normalizedCode;
|
|
177
|
+
if (input.buyQuantity !== undefined)
|
|
178
|
+
patch.buyQuantity = input.buyQuantity;
|
|
179
|
+
if (input.getQuantity !== undefined)
|
|
180
|
+
patch.getQuantity = input.getQuantity;
|
|
181
|
+
if (input.usageLimitTotal !== undefined)
|
|
182
|
+
patch.usageLimitTotal = input.usageLimitTotal;
|
|
183
|
+
if (input.usageLimitPerCustomer !== undefined) {
|
|
184
|
+
patch.usageLimitPerCustomer = input.usageLimitPerCustomer;
|
|
185
|
+
}
|
|
186
|
+
if (input.validFrom !== undefined)
|
|
187
|
+
patch.validFrom = input.validFrom;
|
|
188
|
+
if (input.validUntil !== undefined)
|
|
189
|
+
patch.validUntil = input.validUntil;
|
|
190
|
+
const updated = await this.repo.update(id, patch, ctx);
|
|
191
|
+
if (!updated) {
|
|
192
|
+
return Err(new CommerceNotFoundError("Promotion not found."));
|
|
193
|
+
}
|
|
194
|
+
const afterHooks = this.deps.hooks.resolve("promotions.afterUpdate");
|
|
195
|
+
const hctx = hookContext(actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, ctx?.tx ?? null);
|
|
196
|
+
await runAfterHooks(afterHooks, existing, updated, "update", hctx);
|
|
197
|
+
return Ok(updated);
|
|
198
|
+
}
|
|
134
199
|
async list(filter, actor, ctx) {
|
|
135
200
|
const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
|
|
136
201
|
const all = await this.repo.findAll(orgId, ctx);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/search/repository/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAExE,MAAM,WAAW,gBAAgB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/search/repository/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAExE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/shipping/repository/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAExE,MAAM,WAAW,kBAAkB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/shipping/repository/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAExE,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/tax/repository/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAExE,MAAM,WAAW,aAAa;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/tax/repository/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAExE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-IP resolution for rate limiting.
|
|
3
|
+
*
|
|
4
|
+
* The Node default reads `c.req.raw.socket.remoteAddress` — which is always
|
|
5
|
+
* undefined on edge runtimes (Cloudflare Workers, Deno Deploy, Vercel Edge),
|
|
6
|
+
* collapsing every client onto a single rate-limit key (a trivial DoS). Edge
|
|
7
|
+
* deployments inject `runtime.getClientIp` to read the platform header instead
|
|
8
|
+
* (CF: `cf-connecting-ip`, Vercel Edge: `x-real-ip`, Fly: `fly-client-ip`).
|
|
9
|
+
*/
|
|
10
|
+
export interface ClientIpContext {
|
|
11
|
+
req: {
|
|
12
|
+
raw: unknown;
|
|
13
|
+
header(name: string): string | undefined;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export type ClientIpResolver = (c: ClientIpContext) => string;
|
|
17
|
+
export interface RuntimeConfig {
|
|
18
|
+
/** Resolve the client IP from the request (used as the rate-limit key). */
|
|
19
|
+
getClientIp?: ClientIpResolver;
|
|
20
|
+
/** Direct-connection IP of the trusted reverse proxy (Node default only). */
|
|
21
|
+
trustedProxyIp?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build the client-IP resolver. Prefers an injected `runtime.getClientIp`;
|
|
25
|
+
* otherwise falls back to the Node behavior (socket address, trusting
|
|
26
|
+
* X-Forwarded-For only from `trustedProxyIp` / `TRUSTED_PROXY_IP`).
|
|
27
|
+
*/
|
|
28
|
+
export declare function createClientIpResolver(config: {
|
|
29
|
+
runtime?: RuntimeConfig;
|
|
30
|
+
}): ClientIpResolver;
|
|
31
|
+
//# sourceMappingURL=client-ip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-ip.d.ts","sourceRoot":"","sources":["../../src/runtime/client-ip.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE;QAAE,GAAG,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;CACjE;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,EAAE,eAAe,KAAK,MAAM,CAAC;AAE9D,MAAM,WAAW,aAAa;IAC5B,2EAA2E;IAC3E,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,GAAG,gBAAgB,CAkBnB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-IP resolution for rate limiting.
|
|
3
|
+
*
|
|
4
|
+
* The Node default reads `c.req.raw.socket.remoteAddress` — which is always
|
|
5
|
+
* undefined on edge runtimes (Cloudflare Workers, Deno Deploy, Vercel Edge),
|
|
6
|
+
* collapsing every client onto a single rate-limit key (a trivial DoS). Edge
|
|
7
|
+
* deployments inject `runtime.getClientIp` to read the platform header instead
|
|
8
|
+
* (CF: `cf-connecting-ip`, Vercel Edge: `x-real-ip`, Fly: `fly-client-ip`).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Build the client-IP resolver. Prefers an injected `runtime.getClientIp`;
|
|
12
|
+
* otherwise falls back to the Node behavior (socket address, trusting
|
|
13
|
+
* X-Forwarded-For only from `trustedProxyIp` / `TRUSTED_PROXY_IP`).
|
|
14
|
+
*/
|
|
15
|
+
export function createClientIpResolver(config) {
|
|
16
|
+
if (config.runtime?.getClientIp) {
|
|
17
|
+
return config.runtime.getClientIp;
|
|
18
|
+
}
|
|
19
|
+
const trustedProxyIp = config.runtime?.trustedProxyIp ??
|
|
20
|
+
(typeof process !== "undefined" ? process.env?.TRUSTED_PROXY_IP : undefined);
|
|
21
|
+
return (c) => {
|
|
22
|
+
const raw = c.req.raw;
|
|
23
|
+
const remoteAddress = raw?.socket?.remoteAddress;
|
|
24
|
+
if (trustedProxyIp && remoteAddress === trustedProxyIp) {
|
|
25
|
+
const xff = c.req.header("x-forwarded-for")?.split(",")[0]?.trim();
|
|
26
|
+
if (xff)
|
|
27
|
+
return xff;
|
|
28
|
+
}
|
|
29
|
+
return remoteAddress ?? "unknown";
|
|
30
|
+
};
|
|
31
|
+
}
|
package/dist/runtime/server.d.ts
CHANGED
|
@@ -32,6 +32,10 @@ export declare function createServer(config: CommerceConfig): Promise<{
|
|
|
32
32
|
kernel: import("./kernel-types.js").Kernel;
|
|
33
33
|
logger: Logger;
|
|
34
34
|
commerce: CommerceInstance;
|
|
35
|
+
runJobs: (queue?: string, limit?: number) => Promise<{
|
|
36
|
+
processed: number;
|
|
37
|
+
failed: number;
|
|
38
|
+
}>;
|
|
35
39
|
}>;
|
|
36
40
|
export {};
|
|
37
41
|
//# sourceMappingURL=server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/runtime/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/runtime/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAOhD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAMzD,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAItE,KAAK,SAAS,GAAG;IACf,SAAS,EAAE;QACT,IAAI,EAAE,YAAY,CAAC;QACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAMvC;AAeD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,cAAc;;;;;sBAwVxB,MAAM,UAAU,MAAM;;;;GAgFtD"}
|
package/dist/runtime/server.js
CHANGED
|
@@ -4,6 +4,7 @@ import { csrf } from "hono/csrf";
|
|
|
4
4
|
import { bodyLimit } from "hono/body-limit";
|
|
5
5
|
import { rateLimiter } from "hono-rate-limiter";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
|
+
import { createClientIpResolver } from "./client-ip.js";
|
|
7
8
|
import { authMiddleware } from "../auth/middleware.js";
|
|
8
9
|
import { createRestRoutes } from "../interfaces/rest/index.js";
|
|
9
10
|
import { createCustomerPortalRoutes } from "../interfaces/rest/customer-portal.js";
|
|
@@ -64,12 +65,12 @@ export async function createServer(config) {
|
|
|
64
65
|
if (config.auth?.enableDevKey !== undefined) {
|
|
65
66
|
throw new Error("FATAL: auth.enableDevKey has been removed. " +
|
|
66
67
|
"Use 'bunx @porulle/cli api-key create --scope admin' to generate a real API key. " +
|
|
67
|
-
"See
|
|
68
|
+
"See https://github.com/asyncdotengineering/porulle/blob/main/SECURITY.md for the auth/key scoping rationale.");
|
|
68
69
|
}
|
|
69
70
|
if (config.auth?.devKey !== undefined) {
|
|
70
71
|
throw new Error("FATAL: auth.devKey has been removed. " +
|
|
71
72
|
"Use 'bunx @porulle/cli api-key create --scope admin' to generate a real API key. " +
|
|
72
|
-
"See
|
|
73
|
+
"See https://github.com/asyncdotengineering/porulle/blob/main/SECURITY.md for the auth/key scoping rationale.");
|
|
73
74
|
}
|
|
74
75
|
// exposed for direct unit testing — see test/server-edge-runtime.test.ts
|
|
75
76
|
// ─── Process Crash Handlers (F4) ─────────────────────────────────────
|
|
@@ -143,28 +144,30 @@ export async function createServer(config) {
|
|
|
143
144
|
: (process.env.NODE_ENV === "production" ? [] : ["http://localhost:*"]),
|
|
144
145
|
}));
|
|
145
146
|
// ─── Body Size Limit (F6) ──────────────────────────────────────────
|
|
146
|
-
|
|
147
|
+
// Media uploads (phone photos are 3–8MB) get their own larger limit and are
|
|
148
|
+
// exempt from the global 1MB limit. Everything else stays at 1MB.
|
|
149
|
+
const mediaMaxUploadSize = config.media?.maxUploadSize ?? 10 * 1024 * 1024;
|
|
150
|
+
const MEDIA_UPLOAD_PATH = "/api/media/upload";
|
|
151
|
+
app.use(MEDIA_UPLOAD_PATH, bodyLimit({
|
|
152
|
+
maxSize: mediaMaxUploadSize,
|
|
153
|
+
onError: (c) => c.json({
|
|
154
|
+
error: { code: "FILE_TOO_LARGE", message: `Upload exceeds the ${mediaMaxUploadSize}-byte limit.` },
|
|
155
|
+
}, 413),
|
|
156
|
+
}));
|
|
157
|
+
const globalBodyLimit = bodyLimit({
|
|
147
158
|
maxSize: 1024 * 1024, // 1 MB default
|
|
148
159
|
onError: (c) => c.json({
|
|
149
160
|
error: { code: "PAYLOAD_TOO_LARGE", message: "Request body exceeds 1MB limit." },
|
|
150
161
|
}, 413),
|
|
151
|
-
})
|
|
162
|
+
});
|
|
163
|
+
app.use("*", (c, next) => c.req.path === MEDIA_UPLOAD_PATH ? next() : globalBodyLimit(c, next));
|
|
152
164
|
// ─── Rate Limiting (F1) ──────────────────────────────────────────────
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
|
|
165
|
+
// Client IP drives the rate-limit key. Defaults to the Node socket address
|
|
166
|
+
// (trusting X-Forwarded-For only from a known proxy via config.runtime
|
|
167
|
+
// .trustedProxyIp / TRUSTED_PROXY_IP); edge runtimes inject
|
|
168
|
+
// config.runtime.getClientIp to read the platform header instead.
|
|
156
169
|
const signInEmailRateKeyCache = new WeakMap();
|
|
157
|
-
const getClientIp = (
|
|
158
|
-
const raw = c.req.raw;
|
|
159
|
-
const remoteAddress = raw.socket?.remoteAddress;
|
|
160
|
-
// Only trust X-Forwarded-For when the direct connection is from a trusted proxy
|
|
161
|
-
if (trustedProxyIp && remoteAddress === trustedProxyIp) {
|
|
162
|
-
const xff = c.req.header("x-forwarded-for")?.split(",")[0]?.trim();
|
|
163
|
-
if (xff)
|
|
164
|
-
return xff;
|
|
165
|
-
}
|
|
166
|
-
return remoteAddress ?? "unknown";
|
|
167
|
-
};
|
|
170
|
+
const getClientIp = createClientIpResolver(config);
|
|
168
171
|
const keyGenerator = getClientIp;
|
|
169
172
|
app.use("/api/auth/*", rateLimiter({
|
|
170
173
|
windowMs: 60 * 1000,
|
|
@@ -394,5 +397,18 @@ export async function createServer(config) {
|
|
|
394
397
|
const rewritten = rewriteCommerceAliasRequest(req, config.entities);
|
|
395
398
|
return dispatchFetch(rewritten, env, executionCtx);
|
|
396
399
|
});
|
|
397
|
-
|
|
400
|
+
// `runJobs` triggers one job-runner tick. On Cloudflare Workers, call it
|
|
401
|
+
// from `scheduled()` so cron triggers drive the queue without an in-process
|
|
402
|
+
// setInterval (which can't outlive a request on Workers):
|
|
403
|
+
//
|
|
404
|
+
// // wrangler.toml
|
|
405
|
+
// [triggers]
|
|
406
|
+
// crons = ["*/5 * * * *"]
|
|
407
|
+
//
|
|
408
|
+
// // worker.ts
|
|
409
|
+
// export default {
|
|
410
|
+
// fetch: (req, env, ctx) => server.app.fetch(req, env, ctx),
|
|
411
|
+
// scheduled: (_event, env, ctx) => ctx.waitUntil(server.runJobs()),
|
|
412
|
+
// };
|
|
413
|
+
return { app, kernel, logger, commerce, runJobs };
|
|
398
414
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-plugin-test-app.d.ts","sourceRoot":"","sources":["../../src/test-utils/create-plugin-test-app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"create-plugin-test-app.d.ts","sourceRoot":"","sources":["../../src/test-utils/create-plugin-test-app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAgBnD;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE;QACT,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;KACrB,CAAC;CACH,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,wFAAwF;IACxF,GAAG,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;IAC7B,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,EAAE,EAAE,UAAU,CAAC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC3D;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,cAAc,EACtB,eAAe,GAAE,OAAO,CAAC,cAAc,CAAM,GAC5C,OAAO,CAAC,aAAa,CAAC,CAgFxB"}
|
|
@@ -21,6 +21,7 @@ import { createRequire } from "node:module";
|
|
|
21
21
|
import { createTestConfig } from "./create-test-config.js";
|
|
22
22
|
import { createKernel } from "../runtime/kernel.js";
|
|
23
23
|
import { buildSchema } from "../kernel/database/migrate.js";
|
|
24
|
+
import { unwrapDb } from "../kernel/database/adapter.js";
|
|
24
25
|
import { ensureDefaultOrg } from "../auth/org.js";
|
|
25
26
|
import { mapErrorToStatus } from "../kernel/error-mapper.js";
|
|
26
27
|
// drizzle-kit/api uses CJS internally; createRequire provides ESM compat.
|
|
@@ -46,7 +47,9 @@ export async function createPluginTestApp(plugin, configOverrides = {}) {
|
|
|
46
47
|
// Diffs current DB state against pgTable definitions, generates DDL, applies it.
|
|
47
48
|
// On fresh PGlite: creates all tables. On existing DB: creates only missing tables.
|
|
48
49
|
const drizzleKit = require("drizzle-kit/api");
|
|
49
|
-
const { apply } = await drizzleKit.pushSchema(mergedSchema,
|
|
50
|
+
const { apply } = await drizzleKit.pushSchema(mergedSchema,
|
|
51
|
+
// drizzle-kit needs the native driver result shape; unwrap the normalized db.
|
|
52
|
+
unwrapDb(kernel.database.db));
|
|
50
53
|
await apply();
|
|
51
54
|
// Ensure the default organization exists for plugin tests
|
|
52
55
|
await ensureDefaultOrg(kernel.database.db);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Actor } from "../auth/types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Organization ID used in test fixtures. Tests use the deprecated DEFAULT_ORG_ID
|
|
4
|
-
* because ensureDefaultOrg() creates this org
|
|
5
|
-
*
|
|
4
|
+
* because ensureDefaultOrg() creates this org during test setup. When
|
|
5
|
+
* DEFAULT_ORG_ID is removed, test setup will create a real org via the
|
|
6
|
+
* Better Auth API instead.
|
|
6
7
|
*/
|
|
7
8
|
export declare const TEST_ORG_ID = "org_default";
|
|
8
9
|
/** Admin with wildcard permissions. Use for setup operations in beforeAll. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-actors.d.ts","sourceRoot":"","sources":["../../src/test-utils/test-actors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAG9C
|
|
1
|
+
{"version":3,"file":"test-actors.d.ts","sourceRoot":"","sources":["../../src/test-utils/test-actors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAG9C;;;;;GAKG;AACH,eAAO,MAAM,WAAW,gBAAiB,CAAC;AAE1C,8EAA8E;AAC9E,eAAO,MAAM,cAAc,EAAE,KAS5B,CAAC;AAEF,iDAAiD;AACjD,eAAO,MAAM,cAAc,EAAE,KAY5B,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,KAS/B,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,eAAe,EAAE,KAS7B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIjE"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { DEFAULT_ORG_ID } from "../auth/org.js";
|
|
2
2
|
/**
|
|
3
3
|
* Organization ID used in test fixtures. Tests use the deprecated DEFAULT_ORG_ID
|
|
4
|
-
* because ensureDefaultOrg() creates this org
|
|
5
|
-
*
|
|
4
|
+
* because ensureDefaultOrg() creates this org during test setup. When
|
|
5
|
+
* DEFAULT_ORG_ID is removed, test setup will create a real org via the
|
|
6
|
+
* Better Auth API instead.
|
|
6
7
|
*/
|
|
7
8
|
export const TEST_ORG_ID = DEFAULT_ORG_ID;
|
|
8
9
|
/** Admin with wildcard permissions. Use for setup operations in beforeAll. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@porulle/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -35,15 +35,8 @@
|
|
|
35
35
|
"types": "./src/drizzle.ts"
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
|
-
"scripts": {
|
|
39
|
-
"generate:plugins": "node ./scripts/generate-plugin-types.mjs",
|
|
40
|
-
"check:store-guard": "node ./scripts/check-store-guard.mjs",
|
|
41
|
-
"build": "rm -rf dist tsconfig.build.tsbuildinfo && npm run generate:plugins && npm run check:store-guard && tsc -p tsconfig.build.json",
|
|
42
|
-
"check-types": "npm run generate:plugins && npm run check:store-guard && tsc --noEmit",
|
|
43
|
-
"lint": "eslint . --max-warnings 1000",
|
|
44
|
-
"test": "vitest run"
|
|
45
|
-
},
|
|
46
38
|
"dependencies": {
|
|
39
|
+
"@better-auth/api-key": "^1.3.8",
|
|
47
40
|
"@better-auth/drizzle-adapter": "^1.3.8",
|
|
48
41
|
"@hono/zod-openapi": "^1.2.2",
|
|
49
42
|
"@scalar/hono-api-reference": "^0.10.5",
|
|
@@ -56,16 +49,15 @@
|
|
|
56
49
|
"zod": "^4.1.11"
|
|
57
50
|
},
|
|
58
51
|
"devDependencies": {
|
|
59
|
-
"@better-auth/api-key": "^1.5.4",
|
|
60
52
|
"@electric-sql/pglite": "^0.3.15",
|
|
61
|
-
"@repo/eslint-config": "*",
|
|
62
|
-
"@repo/typescript-config": "*",
|
|
63
53
|
"@types/node": "^25.5.0",
|
|
64
54
|
"@vitest/coverage-v8": "^3.2.4",
|
|
65
55
|
"drizzle-kit": "^0.31.9",
|
|
66
56
|
"eslint": "^9.39.1",
|
|
67
57
|
"typescript": "5.9.2",
|
|
68
|
-
"vitest": "^3.2.4"
|
|
58
|
+
"vitest": "^3.2.4",
|
|
59
|
+
"@porulle/eslint-config": "0.1.0",
|
|
60
|
+
"@porulle/typescript-config": "0.1.0"
|
|
69
61
|
},
|
|
70
62
|
"publishConfig": {
|
|
71
63
|
"access": "public"
|
|
@@ -85,5 +77,13 @@
|
|
|
85
77
|
"url": "git+https://github.com/asyncdotengineering/porulle.git",
|
|
86
78
|
"directory": "packages/core"
|
|
87
79
|
},
|
|
88
|
-
"author": "Porulle contributors"
|
|
89
|
-
|
|
80
|
+
"author": "Porulle contributors",
|
|
81
|
+
"scripts": {
|
|
82
|
+
"generate:plugins": "node ./scripts/generate-plugin-types.mjs",
|
|
83
|
+
"check:store-guard": "node ./scripts/check-store-guard.mjs",
|
|
84
|
+
"build": "rm -rf dist tsconfig.build.tsbuildinfo && npm run generate:plugins && npm run check:store-guard && tsc -p tsconfig.build.json",
|
|
85
|
+
"check-types": "npm run generate:plugins && npm run check:store-guard && tsc --noEmit",
|
|
86
|
+
"lint": "eslint . --max-warnings 1000",
|
|
87
|
+
"test": "vitest run"
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/auth/middleware.ts
CHANGED
|
@@ -146,11 +146,28 @@ export function authMiddleware(
|
|
|
146
146
|
auth.api.verifyApiKey
|
|
147
147
|
) {
|
|
148
148
|
try {
|
|
149
|
+
// Resolve the key's configId from its prefix. Better Auth's apiKey
|
|
150
|
+
// plugin throws "No default api-key configuration found" when named
|
|
151
|
+
// scopes are configured and none is `default`/unset — and verifyApiKey
|
|
152
|
+
// only resolves a named scope (and enforces its configId match) when
|
|
153
|
+
// the configId is forwarded. Match the key's prefix to a configured
|
|
154
|
+
// scope so named-scope keys authenticate instead of silently 401-ing.
|
|
155
|
+
let configId: string | undefined;
|
|
156
|
+
const scopes = config.auth?.apiKeyScopes;
|
|
157
|
+
if (scopes) {
|
|
158
|
+
for (const [scopeId, scope] of Object.entries(scopes)) {
|
|
159
|
+
if (scope.prefix && apiKeyHeader.startsWith(scope.prefix)) {
|
|
160
|
+
configId = scopeId;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
149
166
|
// Better Auth server-side calls require { body: { ... } } wrapper.
|
|
150
167
|
// Returns { valid, error, key: Omit<ApiKey,"key"> | null }.
|
|
151
168
|
// See: https://better-auth.com/docs/plugins/api-key/reference
|
|
152
169
|
const result = await auth.api.verifyApiKey({
|
|
153
|
-
body: { key: apiKeyHeader },
|
|
170
|
+
body: { key: apiKeyHeader, ...(configId ? { configId } : {}) },
|
|
154
171
|
});
|
|
155
172
|
if (result?.valid && result.key) {
|
|
156
173
|
const apiKey = result.key as Record<string, unknown>;
|
|
@@ -3,6 +3,7 @@ import type { CommerceConfig, DefineConfigInput } from "./types.js";
|
|
|
3
3
|
import type { TaskDefinition } from "../kernel/jobs/types.js";
|
|
4
4
|
import { defaultKernelJobTasks } from "../kernel/jobs/builtin-job-tasks.js";
|
|
5
5
|
import { _resetRegisteredPlugins } from "../kernel/plugin/manifest.js";
|
|
6
|
+
import { noopStorageAdapter } from "../modules/media/noop-adapter.js";
|
|
6
7
|
|
|
7
8
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
8
9
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -77,5 +78,11 @@ export async function defineConfig(
|
|
|
77
78
|
jobs: mergeBuiltinJobTasks(config.jobs),
|
|
78
79
|
};
|
|
79
80
|
|
|
81
|
+
// Storage is opt-in: default to a no-op adapter so catalog-only deployments
|
|
82
|
+
// boot with zero storage config. A real adapter (local/S3/R2) enables media.
|
|
83
|
+
if (!config.storage) {
|
|
84
|
+
config = { ...config, storage: noopStorageAdapter };
|
|
85
|
+
}
|
|
86
|
+
|
|
80
87
|
return Object.freeze(config);
|
|
81
88
|
}
|
package/src/config/types.ts
CHANGED
|
@@ -306,9 +306,21 @@ export interface CommerceConfig {
|
|
|
306
306
|
shipping?: ShippingConfig;
|
|
307
307
|
payments?: PaymentAdapter[];
|
|
308
308
|
storage?: StorageAdapter;
|
|
309
|
+
/**
|
|
310
|
+
* Runtime/edge overrides. `getClientIp` lets edge deployments (Workers,
|
|
311
|
+
* Vercel Edge, Fly) resolve the client IP from the platform header instead
|
|
312
|
+
* of the Node socket. Defaults to Node behavior.
|
|
313
|
+
*/
|
|
314
|
+
runtime?: import("../runtime/client-ip.js").RuntimeConfig;
|
|
309
315
|
media?: {
|
|
310
316
|
allowedMimeTypes?: string[];
|
|
311
317
|
allowSvg?: boolean;
|
|
318
|
+
/**
|
|
319
|
+
* Max request body size (bytes) for `POST /api/media/upload`. Phone photos
|
|
320
|
+
* are typically 3–8MB, so this path is exempt from the global 1MB body
|
|
321
|
+
* limit. Defaults to 10MB.
|
|
322
|
+
*/
|
|
323
|
+
maxUploadSize?: number;
|
|
312
324
|
};
|
|
313
325
|
email?: {
|
|
314
326
|
send(input: {
|
package/src/hooks/checkout.ts
CHANGED
|
@@ -596,14 +596,14 @@ export const sendConfirmation: AfterHook<OrderResult> = async ({ result, context
|
|
|
596
596
|
};
|
|
597
597
|
|
|
598
598
|
/**
|
|
599
|
-
* Analytics event recording — no-op
|
|
599
|
+
* Analytics event recording — intentional no-op.
|
|
600
600
|
*
|
|
601
601
|
* The DrizzleAnalyticsAdapter queries source tables (orders, inventory)
|
|
602
|
-
* directly via SQL
|
|
603
|
-
*
|
|
602
|
+
* directly via SQL, so no separate event recording is needed. The export
|
|
603
|
+
* is preserved for backwards compatibility with checkout route imports.
|
|
604
604
|
*/
|
|
605
605
|
export const recordAnalyticsEvent: AfterHook<OrderResult> = async () => {
|
|
606
|
-
// No-op: source tables ARE the analytics events
|
|
606
|
+
// No-op: source tables ARE the analytics events.
|
|
607
607
|
};
|
|
608
608
|
|
|
609
609
|
/**
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,8 @@ export { router } from "./interfaces/rest/router.js";
|
|
|
18
18
|
export { webhookRouter, type WebhookRouterResult } from "./interfaces/rest/webhook-router.js";
|
|
19
19
|
export { isPrivateUrl, isPrivateIp } from "./modules/webhooks/ssrf-guard.js";
|
|
20
20
|
export { createServer } from "./runtime/server.js";
|
|
21
|
+
export { createClientIpResolver } from "./runtime/client-ip.js";
|
|
22
|
+
export type { ClientIpResolver, ClientIpContext, RuntimeConfig } from "./runtime/client-ip.js";
|
|
21
23
|
export { createLogger } from "./runtime/logger.js";
|
|
22
24
|
export type { Logger as PinoLogger } from "./runtime/logger.js";
|
|
23
25
|
export { setupGracefulShutdown } from "./runtime/shutdown.js";
|
|
@@ -125,6 +127,7 @@ export type {
|
|
|
125
127
|
PaymentWebhookEvent,
|
|
126
128
|
} from "./modules/payments/adapter.js";
|
|
127
129
|
export type { StorageAdapter } from "./modules/media/adapter.js";
|
|
130
|
+
export { noopStorageAdapter } from "./modules/media/noop-adapter.js";
|
|
128
131
|
export type {
|
|
129
132
|
SearchAdapter,
|
|
130
133
|
SearchDocument,
|
|
@@ -154,9 +157,23 @@ export type {
|
|
|
154
157
|
TaxVoidParams,
|
|
155
158
|
} from "./modules/tax/adapter.js";
|
|
156
159
|
|
|
157
|
-
export {
|
|
160
|
+
export {
|
|
161
|
+
getSchema,
|
|
162
|
+
buildSchema,
|
|
163
|
+
getTableNames,
|
|
164
|
+
getSchemaFiles,
|
|
165
|
+
pushSchema,
|
|
166
|
+
} from "./kernel/database/migrate.js";
|
|
158
167
|
export { consoleEmailAdapter } from "./adapters/console-email.js";
|
|
159
168
|
|
|
169
|
+
export { promotionTypeEnum, type PromotionType } from "./modules/promotions/schemas.js";
|
|
170
|
+
|
|
171
|
+
export { parseJson, err } from "./interfaces/rest/parse-json.js";
|
|
172
|
+
export type { ValidationIssue, ErrorDetails } from "./interfaces/rest/parse-json.js";
|
|
173
|
+
|
|
174
|
+
export { auditMiddleware } from "./interfaces/rest/audit-middleware.js";
|
|
175
|
+
export type { AuditVars } from "./interfaces/rest/audit-middleware.js";
|
|
176
|
+
|
|
160
177
|
export { runCompensationChain } from "./kernel/compensation/executor.js";
|
|
161
178
|
export type {
|
|
162
179
|
CompensationContext,
|