Make elysia build/run in cluster mode

This commit is contained in:
Dominic Ferrando
2026-06-23 12:35:25 -04:00
parent 736c770647
commit 09d0739055
4 changed files with 238 additions and 227 deletions
+10 -226
View File
@@ -1,227 +1,11 @@
import {
DEFAULT_STANDARDS_DATA,
LevelCalculator,
Standards,
} from "@blade-and-brawn/calculator";
import cluster from 'node:cluster'
import os from 'node:os'
import process from 'node:process'
import {
ActivityPerformanceSchema,
PlayerSchema,
type ActivityPerformance,
type Player,
} from "@blade-and-brawn/domain"
import { cors } from "@elysiajs/cors";
import { Elysia, NotFoundError, status } from "elysia";
import {
PrintfulService,
SyncService,
WebflowService,
Printful,
Webflow,
FetchError,
} from "@blade-and-brawn/commerce";
import zipcodesUs from "zipcodes-us";
import z from "zod";
const levelCalculator = new LevelCalculator(
new Standards(DEFAULT_STANDARDS_DATA),
);
export const app = new Elysia()
.use(
cors({
origin: [
// WEBSITE
// -----------
// production
/^https?:\/\/([a-z0-9-]+\.)?bladeandbrawn\.com$/i,
// testing
/^https?:\/\/([a-z0-9-]+\.)?bladeandbrawn\.webflow\.io$/i,
// PORTAL
// -----------
// production
/^https?:\/\/blade-and-brawn\.fly\.dev$/i,
// development
"http://localhost:5173",
],
}),
)
.error({
FetchError,
})
.onError(async ({ code, error }) => {
switch (code) {
case "FetchError":
console.error(error.message, await error.parse());
return error;
}
})
.onAfterHandle(({ request, set }) => {
console.log(
JSON.stringify({
lvl: "info",
msg: "req",
method: request.method,
path: new URL(request.url).pathname,
status: set.status ?? 200,
}),
);
})
.get("/", () => "Hello world")
// CALCULATOR
.post("/calculate", async ({ body }) => {
return {
levels: levelCalculator.calculate(body.player, body.activityPerformances),
};
}, {
body: z.object({
player: PlayerSchema,
activityPerformances: z.array(ActivityPerformanceSchema)
}),
// afterResponse: ({ query, body, responseValue }) => {
// if (query.log === "true") {
// void fetch("https://kv-logger.xominus.workers.dev", {
// method: "POST",
// headers: { "content-type": "application/json" },
// body: JSON.stringify({
// output: responseValue,
// player: body.player,
// activityPerformances: body.activityPerformances
// }),
// });
// }
// }
})
// COMMERCE
// TODO: protect with api key/token
.group("/products", (app) =>
app
.group("/sync", (app) =>
app
// Sync status
.get("/", async ({ }) => SyncService.state)
// Full sync
.post("/", async ({ }) => {
if (SyncService.state.isSyncing)
return status(409, { error: "Sync already in progress" });
await SyncService.sync();
return { ok: true };
})
// Per-product sync
.post("/:printfulProductId", async ({ params }) => {
if (SyncService.state.isSyncing)
return status(409, { error: "Sync already in progress" });
await SyncService.sync(params.printfulProductId);
return { ok: true };
}, { params: z.object({ printfulProductId: z.number() }) }),
)
.get("/:printfulProductId", async ({ params }) => {
const printfulProductId = +params.printfulProductId;
const printfulProduct =
await PrintfulService.Products.get(printfulProductId);
if (!printfulProduct) {
return new NotFoundError();
}
const webflowProductId =
printfulProduct.sync_product.external_id.split("-")[0];
if (!webflowProductId) {
return new NotFoundError();
}
const webflowProduct =
await WebflowService.Products.get(webflowProductId);
return { printfulProduct, webflowProduct };
}),
)
// WEBHOOKS
.post("/webhook/printful", async ({ body }) => {
const payload = body as Printful.Webhook.EventPayload;
switch (payload.type) {
case Printful.Webhook.Event.ProductUpdated: {
const printfulProduct = payload.data.sync_product;
await SyncService.sync(printfulProduct.id);
break;
}
case Printful.Webhook.Event.ProductDeleted: {
await WebflowService.Products.remove(
payload.data.sync_product.external_id,
);
break;
}
case Printful.Webhook.Event.PackageShipped: {
const webflowOrderId = payload.data.order.external_id;
const shipInfo = payload.data.shipment;
await WebflowService.Orders.update(webflowOrderId, {
shippingTrackingURL: shipInfo.tracking_url,
shippingTracking: shipInfo.tracking_number,
shippingProvider: shipInfo.carrier,
});
await WebflowService.Orders.fulfill(webflowOrderId, {
sendOrderFulfilledEmail: true,
});
break;
}
}
return { ok: true };
})
.post("/webhook/webflow", async ({ request, body, set }) => {
if (!WebflowService.Util.verifyWebflowSignature(request, body)) {
set.status = 400;
return "Invalid signature";
}
const payload = body as Webflow.Webhook.EventPayload;
switch (payload.triggerType) {
case Webflow.Webhook.Event.OrderCreated: {
const webflowOrder = payload.payload;
await PrintfulService.Orders.create({
external_id: webflowOrder.orderId,
// TODO: derive from webflow
shipping: "STANDARD",
recipient: {
name: webflowOrder.shippingAddress.addressee,
address1: webflowOrder.shippingAddress.line1,
address2: webflowOrder.shippingAddress.line2,
city: webflowOrder.shippingAddress.city,
state_code: zipcodesUs.find(
webflowOrder.shippingAddress.postalCode.split(
"-",
)[0] ?? "",
).stateCode,
country_code: webflowOrder.shippingAddress.country,
zip: webflowOrder.shippingAddress.postalCode,
},
items: webflowOrder.purchasedItems.map(
(webflowOrderSku) => ({
external_variant_id: webflowOrderSku.variantId,
quantity: webflowOrderSku.count,
}),
),
});
break;
}
}
return { ok: true };
});
app.listen(3000);
if (cluster.isPrimary) {
for (let i = 0; i < os.availableParallelism(); i++)
cluster.fork()
} else {
await import('./server')
console.log(`Worker ${process.pid} started`)
}
+225
View File
@@ -0,0 +1,225 @@
import {
DEFAULT_STANDARDS_DATA,
LevelCalculator,
Standards,
} from "@blade-and-brawn/calculator";
import {
ActivityPerformanceSchema,
PlayerSchema,
} from "@blade-and-brawn/domain"
import { cors } from "@elysiajs/cors";
import { Elysia, NotFoundError, status } from "elysia";
import {
PrintfulService,
SyncService,
WebflowService,
Printful,
Webflow,
FetchError,
} from "@blade-and-brawn/commerce";
import zipcodesUs from "zipcodes-us";
import z from "zod";
const levelCalculator = new LevelCalculator(
new Standards(DEFAULT_STANDARDS_DATA),
);
export const app = new Elysia()
.use(
cors({
origin: [
// WEBSITE
// -----------
// production
/^https?:\/\/([a-z0-9-]+\.)?bladeandbrawn\.com$/i,
// testing
/^https?:\/\/([a-z0-9-]+\.)?bladeandbrawn\.webflow\.io$/i,
// PORTAL
// -----------
// production
/^https?:\/\/blade-and-brawn\.fly\.dev$/i,
// development
"http://localhost:5173",
],
}),
)
.error({
FetchError,
})
.onError(async ({ code, error }) => {
switch (code) {
case "FetchError":
console.error(error.message, await error.parse());
return error;
}
})
.onAfterHandle(({ request, set }) => {
console.log(
JSON.stringify({
lvl: "info",
msg: "req",
method: request.method,
path: new URL(request.url).pathname,
status: set.status ?? 200,
}),
);
})
.get("/", () => "Hello world")
// CALCULATOR
.post("/calculate", async ({ body }) => {
return {
levels: levelCalculator.calculate(body.player, body.activityPerformances),
};
}, {
body: z.object({
player: PlayerSchema,
activityPerformances: z.array(ActivityPerformanceSchema)
}),
// afterResponse: ({ query, body, responseValue }) => {
// if (query.log === "true") {
// void fetch("https://kv-logger.xominus.workers.dev", {
// method: "POST",
// headers: { "content-type": "application/json" },
// body: JSON.stringify({
// output: responseValue,
// player: body.player,
// activityPerformances: body.activityPerformances
// }),
// });
// }
// }
})
// COMMERCE
// TODO: protect with api key/token
.group("/products", (app) =>
app
.group("/sync", (app) =>
app
// Sync status
.get("/", async ({ }) => SyncService.state)
// Full sync
.post("/", async ({ }) => {
if (SyncService.state.isSyncing)
return status(409, { error: "Sync already in progress" });
await SyncService.sync();
return { ok: true };
})
// Per-product sync
.post("/:printfulProductId", async ({ params }) => {
if (SyncService.state.isSyncing)
return status(409, { error: "Sync already in progress" });
await SyncService.sync(params.printfulProductId);
return { ok: true };
}, { params: z.object({ printfulProductId: z.number() }) }),
)
.get("/:printfulProductId", async ({ params }) => {
const printfulProductId = +params.printfulProductId;
const printfulProduct =
await PrintfulService.Products.get(printfulProductId);
if (!printfulProduct) {
return new NotFoundError();
}
const webflowProductId =
printfulProduct.sync_product.external_id.split("-")[0];
if (!webflowProductId) {
return new NotFoundError();
}
const webflowProduct =
await WebflowService.Products.get(webflowProductId);
return { printfulProduct, webflowProduct };
}),
)
// WEBHOOKS
.post("/webhook/printful", async ({ body }) => {
const payload = body as Printful.Webhook.EventPayload;
switch (payload.type) {
case Printful.Webhook.Event.ProductUpdated: {
const printfulProduct = payload.data.sync_product;
await SyncService.sync(printfulProduct.id);
break;
}
case Printful.Webhook.Event.ProductDeleted: {
await WebflowService.Products.remove(
payload.data.sync_product.external_id,
);
break;
}
case Printful.Webhook.Event.PackageShipped: {
const webflowOrderId = payload.data.order.external_id;
const shipInfo = payload.data.shipment;
await WebflowService.Orders.update(webflowOrderId, {
shippingTrackingURL: shipInfo.tracking_url,
shippingTracking: shipInfo.tracking_number,
shippingProvider: shipInfo.carrier,
});
await WebflowService.Orders.fulfill(webflowOrderId, {
sendOrderFulfilledEmail: true,
});
break;
}
}
return { ok: true };
})
.post("/webhook/webflow", async ({ request, body, set }) => {
if (!WebflowService.Util.verifyWebflowSignature(request, body)) {
set.status = 400;
return "Invalid signature";
}
const payload = body as Webflow.Webhook.EventPayload;
switch (payload.triggerType) {
case Webflow.Webhook.Event.OrderCreated: {
const webflowOrder = payload.payload;
await PrintfulService.Orders.create({
external_id: webflowOrder.orderId,
// TODO: derive from webflow
shipping: "STANDARD",
recipient: {
name: webflowOrder.shippingAddress.addressee,
address1: webflowOrder.shippingAddress.line1,
address2: webflowOrder.shippingAddress.line2,
city: webflowOrder.shippingAddress.city,
state_code: zipcodesUs.find(
webflowOrder.shippingAddress.postalCode.split(
"-",
)[0] ?? "",
).stateCode,
country_code: webflowOrder.shippingAddress.country,
zip: webflowOrder.shippingAddress.postalCode,
},
items: webflowOrder.purchasedItems.map(
(webflowOrderSku) => ({
external_variant_id: webflowOrderSku.variantId,
quantity: webflowOrderSku.count,
}),
),
});
break;
}
}
return { ok: true };
});
app.listen(3000);