This commit is contained in:
John Yeo
2025-09-29 11:15:23 +01:00
parent 1103fd8ca2
commit 3d0e4f94f2
61 changed files with 1820 additions and 261 deletions

View File

@@ -124,9 +124,13 @@
"drizzle-kit": "^0.31.1",
"drizzle-orm": "^0.43.1",
"drizzle-zod": "^0.8.2",
"js-yaml": "^4.1.0",
"yaml": "^2.8.1",
"zod-openapi": "^5.4.1",
},
"devDependencies": {
"@types/bun": "latest",
"@types/js-yaml": "^4.0.9",
"@types/node": "^24.0.3",
"@typescript/native-preview": "^7.0.0-dev.20250731.1",
"cross-env": "^7.0.3",
@@ -1079,7 +1083,7 @@
"@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
"@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
"@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
"@types/bunyan": ["@types/bunyan@1.8.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ=="],
@@ -1129,6 +1133,8 @@
"@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
"@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/lodash": ["@types/lodash@4.17.20", "", {}, "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="],
@@ -2663,6 +2669,8 @@
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
@@ -2681,6 +2689,8 @@
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"zod-openapi": ["zod-openapi@5.4.1", "", { "peerDependencies": { "zod": "^3.25.74 || ^4.0.0" } }, "sha512-deZCmezGJhi2urw7l8LWYCJEUaAb/Azo14c0wncCM7L7u7hz+1d4v3r4Uav7x4+zVkJj/ZYlCu6lwT0QQttW6g=="],
"zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="],
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
@@ -3041,7 +3051,7 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@types/bun/bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
"@types/bun/bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],

View File

@@ -1,31 +1,28 @@
import { config } from "dotenv";
config();
import "./instrumentation.js";
import { trace, context } from "@opentelemetry/api";
import http from "http";
import cluster from "cluster";
import os from "os";
import mainRouter from "./internal/mainRouter.js";
import express from "express";
import cors from "cors";
import webhooksRouter from "./external/webhooks/webhooksRouter.js";
import { apiRouter } from "./internal/api/apiRouter.js";
import { QueueManager } from "./queue/QueueManager.js";
import { AppEnv } from "@autumn/shared";
import { context, trace } from "@opentelemetry/api";
import { toNodeHandler } from "better-auth/node";
import cluster from "cluster";
import cors from "cors";
import express from "express";
import http from "http";
import os from "os";
import { client, db } from "./db/initDrizzle.js";
import { CacheManager } from "./external/caching/CacheManager.js";
import { ClickHouseManager } from "./external/clickhouse/ClickHouseManager.js";
import { logger } from "./external/logtail/logtailUtils.js";
import { createPosthogCli } from "./external/posthog/createPosthogCli.js";
import { generateId } from "./utils/genUtils.js";
import { subscribeToOrgUpdates } from "./external/supabase/subscribeToOrgUpdates.js";
import { client, db } from "./db/initDrizzle.js";
import { toNodeHandler } from "better-auth/node";
import webhooksRouter from "./external/webhooks/webhooksRouter.js";
import { apiRouter } from "./internal/api/apiRouter.js";
import mainRouter from "./internal/mainRouter.js";
import { QueueManager } from "./queue/QueueManager.js";
import { auth } from "./utils/auth.js";
import { generateId } from "./utils/genUtils.js";
import { checkEnvVars } from "./utils/initUtils.js";
import { ClickHouseManager } from "./external/clickhouse/ClickHouseManager.js";
const tracer = trace.getTracer("express");
@@ -171,13 +168,13 @@ if (process.env.NODE_ENV === "development") {
init();
registerShutdownHandlers();
} else {
let numCPUs = os.cpus().length;
const numCPUs = os.cpus().length;
if (cluster.isPrimary) {
console.log(`Master ${process.pid} is running`);
console.log("Number of CPUs", numCPUs);
let numWorkers = 7;
const numWorkers = 7;
for (let i = 0; i < numWorkers; i++) {
cluster.fork();

View File

@@ -1,25 +1,23 @@
import { ErrCode } from "@/errors/errCodes.js";
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
import { APIVersion, type Feature, FeatureType } from "@autumn/shared";
import { Router } from "express";
import { StatusCodes } from "http-status-codes";
import { handleEventSent } from "../events/eventRouter.js";
import { ErrCode } from "@/errors/errCodes.js";
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
import { notNullish } from "@/utils/genUtils.js";
import { handleProductCheck } from "./handlers/handleProductCheck.js";
import { getBooleanEntitledResult } from "./checkUtils.js";
import { getCheckPreview } from "./getCheckPreview.js";
import { orgToVersion } from "@/utils/versionUtils.js";
import { handleEventSent } from "../events/eventRouter.js";
import { getCheckData } from "./checkUtils/getCheckData.js";
import { getV1CheckResponse } from "./checkUtils/getV1CheckResponse.js";
import { getV2CheckResponse } from "./checkUtils/getV2CheckResponse.js";
import { getBooleanEntitledResult } from "./checkUtils.js";
import { getCheckPreview } from "./getCheckPreview.js";
import { handleProductCheck } from "./handlers/handleProductCheck.js";
export const checkRouter: Router = Router();
checkRouter.post("", async (req: any, res: any) => {
try {
let {
const {
customer_id,
feature_id,
product_id,
@@ -71,7 +69,7 @@ checkRouter.post("", async (req: any, res: any) => {
let quantity = 1;
if (notNullish(requiredBalance)) {
let floatQuantity = parseFloat(requiredBalance);
const floatQuantity = parseFloat(requiredBalance);
if (isNaN(floatQuantity)) {
throw new RecaseError({
@@ -93,7 +91,7 @@ checkRouter.post("", async (req: any, res: any) => {
allFeatures,
} = await getCheckData({ req });
let apiVersion = orgToVersion({
const apiVersion = orgToVersion({
org,
reqApiVersion: req.apiVersion,
});
@@ -171,7 +169,7 @@ checkRouter.post("", async (req: any, res: any) => {
}
}
let preview = undefined;
let preview;
if (req.body.with_preview) {
try {
preview = await getCheckPreview({

131
shared/api/customer.ts Normal file
View File

@@ -0,0 +1,131 @@
import { writeFileSync } from "node:fs";
import yaml from "yaml";
import { z } from "zod/v4";
import { createDocument } from "zod-openapi";
import { AttachBodySchema } from "../models/attachModels/attachBody.js";
import { CusResponseSchema } from "../models/cusModels/cusResponseModels.js";
const customerId = z.string().meta({
description: "Your internal ID for the customer",
example: "cus_123",
id: "customerId",
});
const featureId = z.string().meta({
description: "Feature ID as defined in the dashboard (eg. 'messages')",
example: "messages",
id: "featureId",
});
const AttachResult = z
.object({
message: z.string().meta({
description: "A short description on the result of the operation",
example: "Successfully downgraded from Product A to Product B",
id: "message",
}),
product_ids: z.array(z.string()).meta({
description: "The IDs of the products that were attached",
example: ["pro", "one_off"],
id: "productIds",
}),
customer_id: customerId,
})
.meta({ id: "AttachResult" });
const attachDefinition = {
// requestParams: { path: z.object({ customerId }) },
requestBody: {
content: {
"application/json": { schema: AttachBodySchema },
},
},
responses: {
"200": {
description: "200 OK",
content: {
"application/json": { schema: AttachResult },
},
},
},
};
// Define Customer schema as a reusable component
const CustomerV1 = CusResponseSchema.meta({ id: "Customer" });
const document = createDocument({
openapi: "3.1.0",
info: {
title: "My API",
version: "1.0.0",
},
security: [
{
secretKey: [],
},
],
components: {
securitySchemes: {
secretKey: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
},
},
},
paths: {
"/customers/{customerId}": {
get: {
requestParams: { path: z.object({ customerId }) },
responses: {
"200": {
description: "200 OK",
content: {
"application/json": { schema: CustomerV1 },
},
},
},
},
},
"/customers/{customerId}/features/{featureId}": {
patch: {
tags: ["customers.features"],
requestParams: { path: z.object({ customerId, featureId }) },
"x-speakeasy-name-override": "update",
requestBody: {
content: {
"application/json": {
schema: z.object({
usage: z.number(),
}),
},
},
},
responses: {
"200": {
description: "200 OK",
content: {
"application/json": {
schema: z.object({
usage: z.number(),
}),
},
},
},
},
},
},
"/core/attach": { post: attachDefinition },
},
});
// Export to YAML file during build
if (process.env.NODE_ENV !== "production") {
try {
const yamlContent = yaml.stringify(document);
writeFileSync("./openapi.yaml", yamlContent, "utf8");
console.log("OpenAPI document exported to openapi-customer.yaml");
} catch (error) {
console.error("Failed to export OpenAPI document:", error);
}
}

View File

@@ -1,6 +1,6 @@
// Old File
import { z } from "zod";
import { z } from "zod/v4";
import { OrgConfigSchema } from "../models/orgModels/orgConfig.js";
export const StripeConfigSchema = z.object({

View File

@@ -1,89 +1,78 @@
/* TABLES */
import { organizations } from "../models/orgModels/orgTable.js";
import { chatResults } from "../models/chatResultModels/chatResultTable.js";
import { features } from "../models/featureModels/featureTable.js";
// Customer Tables
import { customers } from "../models/cusModels/cusTable.js";
import { entities } from "../models/cusModels/entityModels/entityTable.js";
// Product Tables
import { products } from "../models/productModels/productTable.js";
import { prices } from "../models/productModels/priceModels/priceTable.js";
import { entitlements } from "../models/productModels/entModels/entTable.js";
import { freeTrials } from "../models/productModels/freeTrialModels/freeTrialTable.js";
import { rollovers } from "../models/cusProductModels/cusEntModels/rolloverModels/rolloverTable.js";
import { rolloverRelations } from "../models/cusProductModels/cusEntModels/rolloverModels/rolloverRelations.js";
// CusProduct Tables
import { customerProducts } from "../models/cusProductModels/cusProductTable.js";
import { customerPrices } from "../models/cusProductModels/cusPriceModels/cusPriceTable.js";
import { customerEntitlements } from "../models/cusProductModels/cusEntModels/cusEntTable.js";
// Other Tables
import { apiKeys } from "../models/devModels/apiKeyTable.js";
import { metadata } from "../models/otherModels/metadataTable.js";
import { subscriptions } from "../models/subModels/subTable.js";
import { invoices } from "../models/cusModels/invoiceModels/invoiceTable.js";
import { invoiceRelations } from "../models/cusModels/invoiceModels/invoiceRelations.js";
// Reward Tables
import { rewards } from "../models/rewardModels/rewardModels/rewardTable.js";
import { rewardPrograms } from "../models/rewardModels/rewardProgramModels/rewardProgramTable.js";
import { referralCodes } from "../models/rewardModels/referralModels/referralCodeTable.js";
import { rewardRedemptions } from "../models/rewardModels/referralModels/rewardRedemptionTable.js";
// Migration Tables
import { migrationJobs } from "../models/migrationModels/migrationJobTable.js";
import { migrationErrors } from "../models/migrationModels/migrationErrorTable.js";
/* RELATIONS */
import { organizationsRelations } from "../models/orgModels/orgRelations.js";
import { featureRelations } from "../models/featureModels/featureRelations.js";
// Customer Relations
import { customersRelations } from "../models/cusModels/cusRelations.js";
import { entitiesRelations } from "../models/cusModels/entityModels/entityRelations.js";
// Product Relations
import { entitlementsRelations } from "../models/productModels/entModels/entRelations.js";
import { priceRelations } from "../models/productModels/priceModels/priceRelations.js";
import { productRelations } from "../models/productModels/productRelations.js";
import { freeTrialRelations } from "../models/productModels/freeTrialModels/freeTrialRelations.js";
// CusProduct Relations
import { customerProductsRelations } from "../models/cusProductModels/cusProductRelations.js";
import { customerPricesRelations } from "../models/cusProductModels/cusPriceModels/cusPriceRelations.js";
import { customerEntitlementsRelations } from "../models/cusProductModels/cusEntModels/cusEntRelations.js";
import { apiKeyRelations } from "../models/devModels/apiKeyRelations.js";
import { replaceableRelations } from "../models/cusProductModels/cusEntModels/replaceableRelations.js";
// Reward Relations
import { rewardProgramRelations } from "../models/rewardModels/rewardProgramModels/rewardProgramRelations.js";
import { referralCodeRelations } from "../models/rewardModels/referralModels/referralCodeRelations.js";
import { rewardRedemptionRelations } from "../models/rewardModels/referralModels/rewardRedemptionRelations.js";
// Migration Relations
import { migrationErrorRelations } from "../models/migrationModels/migrationErrorRelations.js";
// Analytics Tables
import { actions } from "../models/analyticsModels/actionTable.js";
import { events } from "../models/eventModels/eventTable.js";
import { replaceables } from "../models/cusProductModels/cusEntModels/replaceableTable.js";
import { chatResults } from "../models/chatResultModels/chatResultTable.js";
// Customer Relations
import { customersRelations } from "../models/cusModels/cusRelations.js";
// Customer Tables
import { customers } from "../models/cusModels/cusTable.js";
import { entitiesRelations } from "../models/cusModels/entityModels/entityRelations.js";
import { entities } from "../models/cusModels/entityModels/entityTable.js";
import { invoiceRelations } from "../models/cusModels/invoiceModels/invoiceRelations.js";
import { invoices } from "../models/cusModels/invoiceModels/invoiceTable.js";
import { customerEntitlementsRelations } from "../models/cusProductModels/cusEntModels/cusEntRelations.js";
import { customerEntitlements } from "../models/cusProductModels/cusEntModels/cusEntTable.js";
import { replaceableRelations } from "../models/cusProductModels/cusEntModels/replaceableRelations.js";
import { replaceables } from "../models/cusProductModels/cusEntModels/replaceableTable.js";
import { rolloverRelations } from "../models/cusProductModels/cusEntModels/rolloverModels/rolloverRelations.js";
import { rollovers } from "../models/cusProductModels/cusEntModels/rolloverModels/rolloverTable.js";
import { customerPricesRelations } from "../models/cusProductModels/cusPriceModels/cusPriceRelations.js";
import { customerPrices } from "../models/cusProductModels/cusPriceModels/cusPriceTable.js";
// CusProduct Relations
import { customerProductsRelations } from "../models/cusProductModels/cusProductRelations.js";
// CusProduct Tables
import { customerProducts } from "../models/cusProductModels/cusProductTable.js";
import { apiKeyRelations } from "../models/devModels/apiKeyRelations.js";
// Other Tables
import { apiKeys } from "../models/devModels/apiKeyTable.js";
import { events } from "../models/eventModels/eventTable.js";
import { featureRelations } from "../models/featureModels/featureRelations.js";
import { features } from "../models/featureModels/featureTable.js";
// Migration Relations
import { migrationErrorRelations } from "../models/migrationModels/migrationErrorRelations.js";
import { migrationErrors } from "../models/migrationModels/migrationErrorTable.js";
// Migration Tables
import { migrationJobs } from "../models/migrationModels/migrationJobTable.js";
/* RELATIONS */
import { organizationsRelations } from "../models/orgModels/orgRelations.js";
import { organizations } from "../models/orgModels/orgTable.js";
import { metadata } from "../models/otherModels/metadataTable.js";
// Product Relations
import { entitlementsRelations } from "../models/productModels/entModels/entRelations.js";
import { entitlements } from "../models/productModels/entModels/entTable.js";
import { freeTrialRelations } from "../models/productModels/freeTrialModels/freeTrialRelations.js";
import { freeTrials } from "../models/productModels/freeTrialModels/freeTrialTable.js";
import { priceRelations } from "../models/productModels/priceModels/priceRelations.js";
import { prices } from "../models/productModels/priceModels/priceTable.js";
import { productRelations } from "../models/productModels/productRelations.js";
// Product Tables
import { products } from "../models/productModels/productTable.js";
import { referralCodeRelations } from "../models/rewardModels/referralModels/referralCodeRelations.js";
import { referralCodes } from "../models/rewardModels/referralModels/referralCodeTable.js";
import { rewardRedemptionRelations } from "../models/rewardModels/referralModels/rewardRedemptionRelations.js";
import { rewardRedemptions } from "../models/rewardModels/referralModels/rewardRedemptionTable.js";
// Reward Tables
import { rewards } from "../models/rewardModels/rewardModels/rewardTable.js";
// Reward Relations
import { rewardProgramRelations } from "../models/rewardModels/rewardProgramModels/rewardProgramRelations.js";
import { rewardPrograms } from "../models/rewardModels/rewardProgramModels/rewardProgramTable.js";
import { subscriptions } from "../models/subModels/subTable.js";
import {
user,
session,
account,
verification,
member,
invitation,
} from "./auth-schema.js";
import {
userRelations,
memberRelations,
inviteRelations,
memberRelations,
userRelations,
} from "./auth-relations.js";
import {
account,
invitation,
member,
session,
user,
verification,
} from "./auth-schema.js";
export {
// Tables

View File

@@ -1,8 +1,8 @@
import { z } from "zod";
import { FeatureOptionsSchema } from "../cusProductModels/cusProductModels.js";
import { ProductItemSchema } from "../productV2Models/productItemModels/productItemModels.js";
import { CreateFreeTrialSchema } from "../productModels/freeTrialModels/freeTrialModels.js";
import { z } from "zod/v4";
import { notNullish } from "../../utils/utils.js";
import { FeatureOptionsSchema } from "../cusProductModels/cusProductModels.js";
import { CreateFreeTrialSchema } from "../productModels/freeTrialModels/freeTrialModels.js";
import { ProductItemSchema } from "../productV2Models/productItemModels/productItemModels.js";
export const ProductOptions = z.object({
product_id: z.string(),

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { FeatureOptionsSchema } from "../cusProductModels/cusProductModels.js";
import { ProductItemResponseSchema } from "../productV2Models/productItemModels/prodItemResponseModels.js";
import { ProductResponseSchema } from "../productV2Models/productResponseModels.js";

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
export const ChatFeatureCreditSchema = z.object({
metered_feature_id: z.string().nonempty(),

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { AppEnv } from "../genModels/genEnums.js";
export const CustomerSchema = z.object({
@@ -21,43 +21,36 @@ export const CreateCustomerSchema = z.object({
.string()
.refine(
(val) => {
if (val === null) return true; // Allow null/undefined
if (val === "") return false;
if (val.includes("@")) return false;
if (val.includes(" ")) return false;
if (val.includes(".")) return false;
return /^[a-zA-Z0-9_-]+$/.test(val);
},
(val) => {
if (val === "") return { message: "can't be an empty string" };
// Check for specific invalid characters and provide targeted messages
if (val.includes("@")) {
{
error: (issue) => {
const input = issue.input as string;
if (input === "") return { message: "can't be an empty string" };
if (input.includes("@"))
return {
message:
"ID cannot contain @ symbol. Use only letters, numbers, underscores, and hyphens.",
};
}
if (val.includes(" ")) {
if (input.includes(" "))
return {
message:
"ID cannot contain spaces. Use only letters, numbers, underscores, and hyphens.",
};
}
if (val.includes(".")) {
if (input.includes("."))
return {
message:
"ID cannot contain periods. Use only letters, numbers, underscores, and hyphens.",
};
}
if (/[^a-zA-Z0-9_-]/.test(val)) {
const invalidChar = val.match(/[^a-zA-Z0-9_-]/)?.[0];
const invalidChar = input.match(/[^a-zA-Z0-9_-]/)?.[0];
return {
message: `ID cannot contain '${invalidChar}'. Use only letters, numbers, underscores, and hyphens.`,
};
}
return {
message:
"ID must contain only letters, numbers, underscores, and hyphens.",
};
},
},
)
.nullish(),

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { EntInterval } from "../../productModels/entModels/entEnums.js";
import { ProductItemFeatureType } from "../../productV2Models/productItemModels/productItemModels.js";

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { CusProductStatus } from "../../cusProductModels/cusProductEnums.js";
import { ProductItemResponseSchema } from "../../productV2Models/productItemModels/prodItemResponseModels.js";

View File

@@ -1,4 +1,4 @@
import z from "zod";
import { z } from "zod/v4";
export const CusReferralResponseSchema = z.object({
program_id: z.string(),

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { DiscountResponseSchema } from "../../rewardModels/rewardModels/rewardResponseModels.js";
export const UpcomingInvoiceResponseSchema = z.object({

View File

@@ -1,4 +1,5 @@
import { z } from "zod";
import { z } from "zod/v4";
import { AppEnv } from "../genModels/genEnums.js";
import { RewardResponseSchema } from "../rewardModels/rewardModels/rewardResponseModels.js";
import { CusProductResponseSchema } from "./cusResModels/cusProductResponse.js";
@@ -17,8 +18,17 @@ export const CusResponseSchema = z.object({
// Internal fields
autumn_id: z.string().nullish(),
id: z.string().nullable(),
created_at: z.number(),
id: z.string().nullable().meta({
description: "Your internal ID for the customer",
example: "cus_123",
}),
created_at: z.number().meta({
description:
"The date and time the customer was created in milliseconds since epoch",
example: 1717000000,
}),
name: z.string().nullable(),
email: z.string().nullable(),
fingerprint: z.string().nullable(),

View File

@@ -1,5 +1,5 @@
import { z } from "zod";
import { Feature } from "../../featureModels/featureModels.js";
import { z } from "zod/v4";
import type { Feature } from "../../featureModels/featureModels.js";
export const EntitySchema = z.object({
id: z.string(),

View File

@@ -1,9 +1,8 @@
import { z } from "zod/v4";
import { AppEnv } from "../../genModels/genEnums.js";
import { z } from "zod";
import { InvoiceResponseSchema } from "../invoiceModels/invoiceResponseModels.js";
import { CusProductResponseSchema } from "../cusResModels/cusProductResponse.js";
import { CusEntResponseV2Schema } from "../cusResModels/cusFeatureResponse.js";
import { CusProductResponseSchema } from "../cusResModels/cusProductResponse.js";
import { InvoiceResponseSchema } from "../invoiceModels/invoiceResponseModels.js";
export const EntityResponseSchema = z.object({
id: z.string().nullable(),

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
const InvoiceItemSchema = z.object({
id: z.string(),

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
export enum InvoiceStatus {
Draft = "draft",

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
export const InvoiceItemResponseSchema = z.object({
description: z.string(),

View File

@@ -1,6 +1,5 @@
import { z } from "zod";
import { z } from "zod/v4";
import { EntitlementWithFeatureSchema } from "../../productModels/entModels/entModels.js";
import { Replaceable } from "./replaceableTable.js";
import { ReplaceableSchema } from "./replaceableSchema.js";
import { RolloverSchema } from "./rolloverModels/rolloverTable.js";

View File

@@ -1,10 +1,9 @@
import { relations } from "drizzle-orm";
import { entitlements } from "../../productModels/entModels/entTable.js";
import { customers } from "../../cusModels/cusTable.js";
import { features } from "../../featureModels/featureTable.js";
import { customerEntitlements } from "./cusEntTable.js";
import { entitlements } from "../../productModels/entModels/entTable.js";
import { customerProducts } from "../cusProductTable.js";
import { customerEntitlements } from "./cusEntTable.js";
import { replaceables } from "./replaceableTable.js";
import { rollovers } from "./rolloverModels/rolloverTable.js";

View File

@@ -1,20 +1,17 @@
import {
pgTable,
numeric,
boolean,
foreignKey,
unique,
text,
jsonb,
index,
jsonb,
numeric,
pgTable,
text,
} from "drizzle-orm/pg-core";
import { features } from "../../featureModels/featureTable.js";
import { collatePgColumn } from "../../../db/utils.js";
import { EntityBalance } from "./cusEntModels.js";
import { features } from "../../featureModels/featureTable.js";
import { entitlements } from "../../productModels/entModels/entTable.js";
import { customerProducts } from "../cusProductTable.js";
import { entitlements } from "../../../db/schema.js";
import type { EntityBalance } from "./cusEntModels.js";
export const customerEntitlements = pgTable(
"customer_entitlements",

View File

@@ -1,6 +1,6 @@
import type { z } from "zod/v4";
import { CusProductSchema, FullCusProductSchema } from "../cusProductModels.js";
import { FullCustomerEntitlementSchema } from "./cusEntModels.js";
import { z } from "zod";
export const FullCusEntWithProductSchema = FullCustomerEntitlementSchema.extend(
{

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { EntitlementSchema } from "../../productModels/entModels/entModels.js";
export const ReplaceableSchema = z.object({

View File

@@ -1,4 +1,4 @@
// import { z } from "zod";
// import { z } from "zod/v4";
// import { EntityBalance, EntityBalanceSchema } from "../../../index.js";
// import { jsonb } from "drizzle-orm/pg-core";

View File

@@ -1,6 +1,6 @@
import { relations } from "drizzle-orm";
import { rollovers } from "./rolloverTable.js";
import { customerEntitlements } from "../cusEntTable.js";
import { rollovers } from "./rolloverTable.js";
export const rolloverRelations = relations(rollovers, ({ one }) => ({
customer_entitlement: one(customerEntitlements, {

View File

@@ -1,13 +1,13 @@
import { z } from "zod";
import { customerEntitlements } from "../cusEntTable.js";
import {
foreignKey,
pgTable,
numeric,
jsonb,
text,
index,
jsonb,
numeric,
pgTable,
text,
} from "drizzle-orm/pg-core";
import { z } from "zod/v4";
import { customerEntitlements } from "../cusEntTable.js";
export const EntityRolloverBalanceSchema = z.object({
id: z.string(),

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { PriceSchema } from "../../productModels/priceModels/priceModels.js";
export const CustomerPriceSchema = z.object({

View File

@@ -1,21 +1,11 @@
import { z } from "zod";
import { ProcessorType } from "../genModels/genEnums.js";
import { ProductSchema } from "../productModels/productModels.js";
import { PriceSchema } from "../productModels/priceModels/priceModels.js";
import {
CustomerEntitlementSchema,
FullCustomerEntitlementSchema,
} from "./cusEntModels/cusEntModels.js";
import { EntitlementSchema } from "../productModels/entModels/entModels.js";
import { FeatureSchema } from "../featureModels/featureModels.js";
import { z } from "zod/v4";
import { CustomerSchema } from "../cusModels/cusModels.js";
import { ProcessorType } from "../genModels/genEnums.js";
import { FreeTrialSchema } from "../productModels/freeTrialModels/freeTrialModels.js";
import {
CustomerPriceSchema,
FullCustomerPriceSchema,
} from "./cusPriceModels/cusPriceModels.js";
import { CollectionMethod } from "./cusProductEnums.js";
import { CusProductStatus } from "./cusProductEnums.js";
import { ProductSchema } from "../productModels/productModels.js";
import { FullCustomerEntitlementSchema } from "./cusEntModels/cusEntModels.js";
import { FullCustomerPriceSchema } from "./cusPriceModels/cusPriceModels.js";
import { CollectionMethod, CusProductStatus } from "./cusProductEnums.js";
export const FeatureOptionsSchema = z.object({
feature_id: z.string(),

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
// export const EventSchema = z.object({
// // Submitted by the client

View File

@@ -1,5 +1,5 @@
import { z } from "zod/v4";
import { FeatureUsageType } from "../featureEnums.js";
import { z } from "zod";
export const CreditSchemaItemSchema = z.object({
metered_feature_id: z.string(),

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { FeatureUsageType } from "../featureEnums.js";
export interface Expression {

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { AppEnv } from "../genModels/genEnums.js";
import { FeatureType } from "./featureEnums.js";

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
export enum APIFeatureType {
Boolean = "boolean",

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { AppEnv } from "../genModels/genEnums.js";
export enum MigrationJobStep {

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
export const FrontendOrgSchema = z.object({
id: z.string(),

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
export const OrgConfigSchema = z.object({
bill_upgrade_immediately: z.boolean().default(true),

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
export const AutumnMetadataSchema = z.object({
id: z.string(),

View File

@@ -1,7 +1,7 @@
import { z } from "zod";
import { z } from "zod/v4";
import { FeatureSchema } from "../../featureModels/featureModels.js";
import { EntInterval } from "./entEnums.js";
import { RolloverConfigSchema } from "../../productV2Models/productItemModels/productItemModels.js";
import { EntInterval } from "./entEnums.js";
export enum AllowanceType {
Fixed = "fixed",

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { FreeTrialDuration } from "./freeTrialEnums.js";
export const FreeTrialSchema = z.object({

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { BillingInterval } from "../priceEnums.js";
export const FixedPriceConfigSchema = z.object({

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { Infinite } from "../../productEnums.js";
import { BillingInterval } from "../priceEnums.js";

View File

@@ -1,9 +1,11 @@
import { z } from "zod";
import { UsagePriceConfigSchema } from "./priceConfig/usagePriceConfig.js";
import { z } from "zod/v4";
import {
OnDecrease,
OnIncrease,
} from "../../productV2Models/productItemModels/productItemEnums.js";
import { FixedPriceConfigSchema } from "./priceConfig/fixedPriceConfig.js";
import { UsagePriceConfigSchema } from "./priceConfig/usagePriceConfig.js";
import { BillingType } from "./priceEnums.js";
import { OnDecrease } from "../../productV2Models/productItemModels/productItemEnums.js";
import { OnIncrease } from "../../productV2Models/productItemModels/productItemEnums.js";
const ProrationConfigSchema = z.object({
on_increase: z.nativeEnum(OnIncrease).default(OnIncrease.ProrateImmediately),

View File

@@ -1,9 +1,9 @@
import { z } from "zod";
import { PriceSchema } from "./priceModels/priceModels.js";
import { EntitlementSchema } from "./entModels/entModels.js";
import { z } from "zod/v4";
import { FeatureSchema } from "../featureModels/featureModels.js";
import { FreeTrialSchema } from "./freeTrialModels/freeTrialModels.js";
import { AppEnv } from "../genModels/genEnums.js";
import { EntitlementSchema } from "./entModels/entModels.js";
import { FreeTrialSchema } from "./freeTrialModels/freeTrialModels.js";
import { PriceSchema } from "./priceModels/priceModels.js";
export const ProductSchema = z.object({
id: z.string(),

View File

@@ -1,4 +1,4 @@
// import { z } from "zod";
// import { z } from "zod/v4";
// import { UsageModel } from "../productItemModels.js";
// export const CheckProdItemSchema = z.object({

View File

@@ -1,6 +1,6 @@
import { ProductItemSchema } from "./productItemModels.js";
import { z } from "zod/v4";
import { Infinite } from "../../productModels/productEnums.js";
import { z } from "zod";
import { ProductItemSchema } from "./productItemModels.js";
export const FeatureItemSchema = ProductItemSchema.pick({
feature_id: true,

View File

@@ -1,5 +1,5 @@
import { z } from "zod/v4";
import { ProductItemSchema } from "./productItemModels.js";
import { z } from "zod";
export const FeaturePriceItemSchema = ProductItemSchema.pick({
feature_id: true,

View File

@@ -1,5 +1,5 @@
import { z } from "zod/v4";
import { ProductItemSchema } from "./productItemModels.js";
import { z } from "zod";
export const PriceItemSchema = ProductItemSchema.pick({
price: true,

View File

@@ -1,4 +1,6 @@
import { z } from "zod";
import { z } from "zod/v4";
import { APIFeatureSchema } from "../../featureModels/featureResModels.js";
import { Infinite } from "../../productModels/productEnums.js";
import {
PriceTierSchema,
ProductItemFeatureType,
@@ -6,8 +8,6 @@ import {
ProductItemType,
UsageModel,
} from "./productItemModels.js";
import { Infinite } from "../../productModels/productEnums.js";
import { APIFeatureSchema } from "../../featureModels/featureResModels.js";
export const ProductItemResponseSchema = z.object({
// Feature stuff

View File

@@ -1,7 +1,6 @@
import { z } from "zod";
import { z } from "zod/v4";
import { Infinite } from "../../productModels/productEnums.js";
import { OnIncrease } from "./productItemEnums.js";
import { OnDecrease } from "./productItemEnums.js";
import { OnDecrease, OnIncrease } from "./productItemEnums.js";
export const TierInfinite = "inf";

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { AttachScenario } from "../checkModels/checkPreviewModels.js";
import { AppEnv } from "../genModels/genEnums.js";
import { FreeTrialResponseSchema } from "../productModels/freeTrialModels/freeTrialModels.js";

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { FreeTrialSchema } from "../productModels/freeTrialModels/freeTrialModels.js";
import { ProductItemSchema } from "./productItemModels/productItemModels.js";

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
const ReferralCodeSchema = z.object({
// Code, org id, env should be unique

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { CouponDurationType, RewardType } from "./rewardEnums.js";
const PromoCodeSchema = z.object({

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { CouponDurationType, RewardType } from "./rewardEnums.js";
export const DiscountResponseSchema = z.object({

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import type { Reward } from "../rewardModels/rewardModels.js";
import { RewardReceivedBy, RewardTriggerEvent } from "./rewardProgramEnums.js";

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { AppEnv } from "../genModels/genEnums.js";
export const SubscriptionSchema = z.object({

1445
shared/openapi.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@
"author": "Recase Inc.",
"license": "Apache-2.0",
"scripts": {
"build:tsc": "tsc",
"build:tsc": "tsc && bun api/customer.ts",
"build": "bun build ./index.ts --outdir dist --format esm --target bun --external zod",
"dev": "bunx nodemon --ext ts --ignore dist --exec \"bun run build && bun run dev:dts\"",
"dev:dts": "tsc --emitDeclarationOnly --outDir dist --project tsconfig.json",
@@ -29,7 +29,8 @@
"dotenv": "^16.5.0",
"drizzle-kit": "^0.31.1",
"drizzle-orm": "^0.43.1",
"drizzle-zod": "^0.8.2"
"drizzle-zod": "^0.8.2",
"zod-openapi": "^5.4.1"
},
"peerDependencies": {
"zod": "^3.25.23"

View File

@@ -1,17 +1,17 @@
import { FeatureType } from "../../../models/featureModels/featureEnums.js";
import {
AllowanceType,
EntitlementWithFeature,
type EntitlementWithFeature,
} from "../../../models/productModels/entModels/entModels.js";
import { FixedPriceConfig } from "../../../models/productModels/priceModels/priceConfig/fixedPriceConfig.js";
import type { FixedPriceConfig } from "../../../models/productModels/priceModels/priceConfig/fixedPriceConfig.js";
import {
BillWhen,
UsagePriceConfig,
type UsagePriceConfig,
} from "../../../models/productModels/priceModels/priceConfig/usagePriceConfig.js";
import { Price } from "../../../models/productModels/priceModels/priceModels.js";
import type { Price } from "../../../models/productModels/priceModels/priceModels.js";
import { Infinite } from "../../../models/productModels/productEnums.js";
import {
ProductItem,
type ProductItem,
ProductItemFeatureType,
TierInfinite,
UsageModel,
@@ -74,8 +74,8 @@ export const toFeaturePriceItem = ({
ent: EntitlementWithFeature;
price: Price;
}) => {
let config = price.config as UsagePriceConfig;
let tiers = config.usage_tiers.map((tier) => {
const config = price.config as UsagePriceConfig;
const tiers = config.usage_tiers.map((tier) => {
return {
amount: tier.amount,
to: tier.to == -1 ? TierInfinite : tier.to,
@@ -91,7 +91,7 @@ export const toFeaturePriceItem = ({
itemConfig.rollover = ent.rollover;
}
let item: ProductItem = {
const item: ProductItem = {
feature_id: ent.feature.id,
feature_type:
ent.feature.config?.usage_type || ProductItemFeatureType.SingleUse,
@@ -127,7 +127,7 @@ export const toFeaturePriceItem = ({
};
export const toPriceItem = ({ price }: { price: Price }) => {
let config = price.config as FixedPriceConfig;
const config = price.config as FixedPriceConfig;
return {
feature_id: null,