diff --git a/apps/api/drizzle.config.ts b/apps/api/drizzle.config.ts index 52b74a82..602bd55c 100644 --- a/apps/api/drizzle.config.ts +++ b/apps/api/drizzle.config.ts @@ -5,7 +5,7 @@ import { defineConfig } from 'drizzle-kit' export default defineConfig({ out: './drizzle', - schema: './src/db/schema/index.ts', + schema: './src/db/schema.ts', dialect: 'postgresql', dbCredentials: { url: process.env.DATABASE_URL! diff --git a/apps/api/eslint.config.js b/apps/api/eslint.config.js index e3403519..41bbc990 100644 --- a/apps/api/eslint.config.js +++ b/apps/api/eslint.config.js @@ -10,7 +10,10 @@ export default [ drizzle }, rules: { - ...drizzle.configs.recommended.rules + ...drizzle.configs.recommended.rules, + 'no-console': 'error', + 'unicorn/no-array-reduce': 'off', + 'no-restricted-imports': ['error', '@agentic/platform-db'] } } ] diff --git a/apps/api/package.json b/apps/api/package.json index c04594fc..455b8e59 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -23,29 +23,23 @@ "clean": "del dist", "test": "run-s test:*", "test:lint": "eslint .", - "test:typecheck": "tsc --noEmit", - "test:unit": "vitest run" + "test:typecheck": "tsc --noEmit" }, "dependencies": { "@agentic/platform-core": "workspace:*", - "@agentic/validators": "workspace:*", - "@fisch0920/drizzle-orm": "^0.43.7", - "@fisch0920/drizzle-zod": "^0.7.9", + "@agentic/platform-db": "workspace:*", + "@agentic/platform-validators": "workspace:*", "@hono/node-server": "^1.14.1", "@hono/sentry": "^1.2.1", "@hono/zod-openapi": "^0.19.6", - "@hono/zod-validator": "^0.5.0", - "@paralleldrive/cuid2": "^2.2.2", "@redocly/openapi-core": "^1.34.3", "@sentry/node": "^9.19.0", - "bcryptjs": "^3.0.2", "eventid": "^2.0.1", "exit-hook": "catalog:", "hono": "^4.7.9", "jsonwebtoken": "^9.0.2", "p-all": "^5.0.0", "parse-json": "^8.3.0", - "postgres": "^3.4.5", "restore-cursor": "catalog:", "semver": "^7.7.2", "stripe": "^18.1.0", diff --git a/apps/api/src/api-v1/consumers/schemas.ts b/apps/api/src/api-v1/consumers/schemas.ts index 28e01548..ab8cf2a6 100644 --- a/apps/api/src/api-v1/consumers/schemas.ts +++ b/apps/api/src/api-v1/consumers/schemas.ts @@ -1,7 +1,10 @@ import { z } from '@hono/zod-openapi' -import { consumerIdSchema, paginationSchema } from '@/db' -import { consumerRelationsSchema } from '@/db/schema' +import { + consumerIdSchema, + consumerRelationsSchema, + paginationSchema +} from '@/db' export const consumerIdParamsSchema = z.object({ consumerId: consumerIdSchema.openapi({ diff --git a/apps/api/src/api-v1/deployments/create-deployment.ts b/apps/api/src/api-v1/deployments/create-deployment.ts index d8270a27..7a9150c8 100644 --- a/apps/api/src/api-v1/deployments/create-deployment.ts +++ b/apps/api/src/api-v1/deployments/create-deployment.ts @@ -1,5 +1,5 @@ import { assert, parseZodSchema, pick, sha256 } from '@agentic/platform-core' -import { validators } from '@agentic/validators' +import { validators } from '@agentic/platform-validators' import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' diff --git a/apps/api/src/api-v1/deployments/schemas.ts b/apps/api/src/api-v1/deployments/schemas.ts index a5656e8e..dd7715a7 100644 --- a/apps/api/src/api-v1/deployments/schemas.ts +++ b/apps/api/src/api-v1/deployments/schemas.ts @@ -1,7 +1,11 @@ import { z } from '@hono/zod-openapi' -import { deploymentIdSchema, paginationSchema, projectIdSchema } from '@/db' -import { deploymentRelationsSchema } from '@/db/schema' +import { + deploymentIdSchema, + deploymentRelationsSchema, + paginationSchema, + projectIdSchema +} from '@/db' export const deploymentIdParamsSchema = z.object({ deploymentId: deploymentIdSchema.openapi({ diff --git a/apps/api/src/api-v1/index.ts b/apps/api/src/api-v1/index.ts index 12ae98ef..2aaff468 100644 --- a/apps/api/src/api-v1/index.ts +++ b/apps/api/src/api-v1/index.ts @@ -110,35 +110,36 @@ apiV1.route('/', privateRouter) // API route types to be used by Hono's RPC client. // Should include all routes except for internal and admin routes. -export type ApiRoutes = - | ReturnType - // Users - | ReturnType - | ReturnType - // Teams - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - // Team members - | ReturnType - | ReturnType - | ReturnType - // Projects - | ReturnType - | ReturnType - | ReturnType - | ReturnType - // Consumers - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - // Deployments - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType +// NOTE: Removing for now because Hono's RPC client / types are clunky and slow. +// export type ApiRoutes = +// | ReturnType +// // Users +// | ReturnType +// | ReturnType +// // Teams +// | ReturnType +// | ReturnType +// | ReturnType +// | ReturnType +// | ReturnType +// // Team members +// | ReturnType +// | ReturnType +// | ReturnType +// // Projects +// | ReturnType +// | ReturnType +// | ReturnType +// | ReturnType +// // Consumers +// | ReturnType +// | ReturnType +// | ReturnType +// | ReturnType +// | ReturnType +// // Deployments +// | ReturnType +// | ReturnType +// | ReturnType +// | ReturnType +// | ReturnType diff --git a/apps/api/src/api-v1/projects/schemas.ts b/apps/api/src/api-v1/projects/schemas.ts index 80e3ce1f..ef873c48 100644 --- a/apps/api/src/api-v1/projects/schemas.ts +++ b/apps/api/src/api-v1/projects/schemas.ts @@ -1,7 +1,6 @@ import { z } from '@hono/zod-openapi' -import { paginationSchema, projectIdSchema } from '@/db' -import { projectRelationsSchema } from '@/db/schema' +import { paginationSchema, projectIdSchema, projectRelationsSchema } from '@/db' export const projectIdParamsSchema = z.object({ projectId: projectIdSchema.openapi({ diff --git a/apps/api/src/api-v1/index.test.ts b/apps/api/src/api-v1/temp similarity index 100% rename from apps/api/src/api-v1/index.test.ts rename to apps/api/src/api-v1/temp diff --git a/apps/api/src/api-v1/webhooks/stripe-webhook.ts b/apps/api/src/api-v1/webhooks/stripe-webhook.ts index 60878fe7..286d97ea 100644 --- a/apps/api/src/api-v1/webhooks/stripe-webhook.ts +++ b/apps/api/src/api-v1/webhooks/stripe-webhook.ts @@ -1,6 +1,6 @@ import type { OpenAPIHono } from '@hono/zod-openapi' import type Stripe from 'stripe' -import { assert,HttpError } from '@agentic/platform-core' +import { assert, HttpError } from '@agentic/platform-core' import { and, db, eq, schema } from '@/db' import { env, isStripeLive } from '@/lib/env' diff --git a/apps/api/src/db/index.ts b/apps/api/src/db/index.ts index 550723cc..60ae2b18 100644 --- a/apps/api/src/db/index.ts +++ b/apps/api/src/db/index.ts @@ -1,44 +1,20 @@ -import { drizzle } from '@fisch0920/drizzle-orm/postgres-js' -import postgres from 'postgres' +// The only place we allow `@agentic/platform-db` imports is in this directory. + + +import { + drizzle, + postgres, + type PostgresClient, + schema +} from '@agentic/platform-db' import { env } from '@/lib/env' -import * as schema from './schema' - -let _postgresClient: ReturnType | undefined +let _postgresClient: PostgresClient | undefined const postgresClient = _postgresClient ?? (_postgresClient = postgres(env.DATABASE_URL)) export const db = drizzle({ client: postgresClient, schema }) -export * as schema from './schema' -export * from './schemas' -export type * from './types' -export { - and, - arrayContained, - arrayContains, - arrayOverlaps, - asc, - between, - desc, - eq, - exists, - gt, - gte, - ilike, - inArray, - isNotNull, - isNull, - like, - lt, - lte, - ne, - not, - notBetween, - notExists, - notIlike, - notInArray, - notLike, - or -} from '@fisch0920/drizzle-orm' + +export * from '@agentic/platform-db' diff --git a/apps/api/src/db/schema.ts b/apps/api/src/db/schema.ts new file mode 100644 index 00000000..76b4c213 --- /dev/null +++ b/apps/api/src/db/schema.ts @@ -0,0 +1,4 @@ +// The only place we allow `@agentic/platform-db` imports is in this directory. + + +export * from '@agentic/platform-db' diff --git a/apps/api/src/lib/billing/upsert-consumer.ts b/apps/api/src/lib/billing/upsert-consumer.ts index faa0cb7f..97f5dbb3 100644 --- a/apps/api/src/lib/billing/upsert-consumer.ts +++ b/apps/api/src/lib/billing/upsert-consumer.ts @@ -1,5 +1,5 @@ import { assert } from '@agentic/platform-core' -import { parseFaasIdentifier } from '@agentic/validators' +import { parseFaasIdentifier } from '@agentic/platform-validators' import type { AuthenticatedContext } from '@/lib/types' import { and, db, eq, schema } from '@/db' diff --git a/apps/api/src/lib/billing/upsert-stripe-pricing.ts b/apps/api/src/lib/billing/upsert-stripe-pricing.ts index c6b3533c..82880500 100644 --- a/apps/api/src/lib/billing/upsert-stripe-pricing.ts +++ b/apps/api/src/lib/billing/upsert-stripe-pricing.ts @@ -2,14 +2,18 @@ import type Stripe from 'stripe' import { assert } from '@agentic/platform-core' import pAll from 'p-all' -import { db, eq, type RawDeployment, type RawProject, schema } from '@/db' import { + db, + eq, getLabelForPricingInterval, getPricingPlanLineItemHashForStripePrice, getPricingPlansByInterval, type PricingPlan, - type PricingPlanLineItem -} from '@/db/schema' + type PricingPlanLineItem, + type RawDeployment, + type RawProject, + schema +} from '@/db' import { stripe } from '@/lib/stripe' /** diff --git a/apps/api/src/lib/billing/upsert-stripe-subscription.ts b/apps/api/src/lib/billing/upsert-stripe-subscription.ts index bc8bddfb..bccdbe71 100644 --- a/apps/api/src/lib/billing/upsert-stripe-subscription.ts +++ b/apps/api/src/lib/billing/upsert-stripe-subscription.ts @@ -6,13 +6,13 @@ import { type ConsumerUpdate, db, eq, + getStripePriceIdForPricingPlanLineItem, type RawConsumer, type RawDeployment, type RawProject, type RawUser, schema } from '@/db' -import { getStripePriceIdForPricingPlanLineItem } from '@/db/schema' import { stripe } from '@/lib/stripe' export async function upsertStripeSubscription( diff --git a/apps/api/src/lib/deployments/normalize-deployment-version.ts b/apps/api/src/lib/deployments/normalize-deployment-version.ts index c6e44bfa..96a9597d 100644 --- a/apps/api/src/lib/deployments/normalize-deployment-version.ts +++ b/apps/api/src/lib/deployments/normalize-deployment-version.ts @@ -1,7 +1,7 @@ import { assert } from '@agentic/platform-core' import semver from 'semver' -import type { RawProject } from '@/db/types' +import type { RawProject } from '@/db' export function normalizeDeploymentVersion({ deploymentId, diff --git a/apps/api/src/lib/deployments/try-get-deployment.ts b/apps/api/src/lib/deployments/try-get-deployment.ts index d1bd79fd..111b2c42 100644 --- a/apps/api/src/lib/deployments/try-get-deployment.ts +++ b/apps/api/src/lib/deployments/try-get-deployment.ts @@ -1,5 +1,5 @@ import { assert } from '@agentic/platform-core' -import { parseFaasIdentifier } from '@agentic/validators' +import { parseFaasIdentifier } from '@agentic/platform-validators' import type { AuthenticatedContext } from '@/lib/types' import { db, eq, type RawDeployment, schema } from '@/db' diff --git a/apps/api/src/lib/deployments/validate-deployment-origin-adapter.ts b/apps/api/src/lib/deployments/validate-deployment-origin-adapter.ts index abe37dd2..d94c6160 100644 --- a/apps/api/src/lib/deployments/validate-deployment-origin-adapter.ts +++ b/apps/api/src/lib/deployments/validate-deployment-origin-adapter.ts @@ -1,6 +1,6 @@ import { assert } from '@agentic/platform-core' -import type { DeploymentOriginAdapter } from '@/db/schema' +import type { DeploymentOriginAdapter } from '@/db' import type { Logger } from '@/lib/logger' import { validateOpenAPISpec } from '@/lib/validate-openapi-spec' diff --git a/apps/api/src/lib/middleware/team.ts b/apps/api/src/lib/middleware/team.ts index 0b7a5d7a..9fde9592 100644 --- a/apps/api/src/lib/middleware/team.ts +++ b/apps/api/src/lib/middleware/team.ts @@ -1,9 +1,8 @@ import { assert } from '@agentic/platform-core' -import { and, eq } from '@fisch0920/drizzle-orm' import { createMiddleware } from 'hono/factory' import type { AuthenticatedEnv } from '@/lib/types' -import { db, schema } from '@/db' +import { and, db, eq, schema } from '@/db' import { aclTeamMember } from '@/lib/acl-team-member' export const team = createMiddleware( diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index bdf45613..11888560 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -38,5 +38,3 @@ initExitHooks({ server }) // eslint-disable-next-line no-console console.log(`Server running on port ${env.PORT}`) - -export { type ApiRoutes } from '@/api-v1' diff --git a/packages/cli/package.json b/packages/cli/package.json index 9b46f507..76282ab2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -22,8 +22,7 @@ "scripts": { "test": "run-s test:*", "test:lint": "eslint .", - "test:typecheck": "tsc --noEmit", - "test:unit": "vitest run" + "test:typecheck": "tsc --noEmit" }, "dependencies": { "@agentic/platform-core": "workspace:*", @@ -35,6 +34,7 @@ }, "devDependencies": { "@agentic/platform-api": "workspace:*", + "@agentic/platform-db": "workspace:*", "@commander-js/extra-typings": "^14.0.0" }, "publishConfig": { diff --git a/packages/cli/src/commands/signin.ts b/packages/cli/src/commands/signin.ts index d96237af..c7dac6e3 100644 --- a/packages/cli/src/commands/signin.ts +++ b/packages/cli/src/commands/signin.ts @@ -8,6 +8,8 @@ export const signin = new Command('login') .option('-u, --username ', 'account username') .option('-e, --email ', 'account email') .option('-p, --password ', 'account password') - .action(async (opts) => { + .action(async (_opts) => { + // TODO + // eslint-disable-next-line no-console console.log('TODO: signin') }) diff --git a/packages/cli/src/commands/whoami.ts b/packages/cli/src/commands/whoami.ts index af522a76..65b6ada6 100644 --- a/packages/cli/src/commands/whoami.ts +++ b/packages/cli/src/commands/whoami.ts @@ -1,11 +1,14 @@ import { Command } from 'commander' -import { getAuth, requireAuth } from '../store' + +import { getAuth } from '../store' export const whoami = new Command('whoami') .description('Displays info about the current user') - .action(async (opts) => { + .action(async () => { const auth = getAuth() + // TODO + // eslint-disable-next-line no-console console.log( JSON.stringify({ user: auth.user, team: auth.teamSlug }, null, 2) ) diff --git a/packages/cli/src/store.ts b/packages/cli/src/store.ts index b3dd7682..04fa93eb 100644 --- a/packages/cli/src/store.ts +++ b/packages/cli/src/store.ts @@ -1,3 +1,4 @@ +import type { User } from '@agentic/platform-db' import { assert } from '@agentic/platform-core' import Conf from 'conf' @@ -5,7 +6,7 @@ export const store = new Conf({ projectName: 'agentic' }) export type Auth = { token: string - user: string + user: User teamId?: string teamSlug?: string } diff --git a/packages/db/package.json b/packages/db/package.json new file mode 100644 index 00000000..a46f1dfa --- /dev/null +++ b/packages/db/package.json @@ -0,0 +1,43 @@ +{ + "name": "@agentic/platform-db", + "version": "0.0.1", + "description": "Postgres database schemas and types for the Agentic platform using Drizzle as the ORM.", + "author": "Travis Fischer ", + "license": "UNLICENSED", + "repository": { + "type": "git", + "url": "git+https://github.com/transitive-bullshit/agentic-platform.git", + "directory": "packages/db" + }, + "type": "module", + "main": "./src/index.ts", + "source": "./src/index.ts", + "types": "./src/index.ts", + "sideEffects": false, + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "test": "run-s test:*", + "test:lint": "eslint .", + "test:typecheck": "tsc --noEmit", + "test:unit": "vitest run" + }, + "dependencies": { + "@agentic/platform-core": "workspace:*", + "@agentic/platform-validators": "workspace:*", + "@fisch0920/drizzle-orm": "^0.43.7", + "@fisch0920/drizzle-zod": "^0.7.9", + "@hono/zod-openapi": "^0.19.6", + "@paralleldrive/cuid2": "^2.2.2", + "bcryptjs": "^3.0.2", + "hono": "^4.7.9", + "parse-json": "^8.3.0", + "postgres": "^3.4.5", + "type-fest": "catalog:", + "zod": "catalog:" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts new file mode 100644 index 00000000..5bd25bd3 --- /dev/null +++ b/packages/db/src/index.ts @@ -0,0 +1,40 @@ +import type postgres from 'postgres' + +export { drizzle } from '@fisch0920/drizzle-orm/postgres-js' + +export type PostgresClient = ReturnType + +export * as schema from './schema' +export * from './schema/schemas' +export * from './schemas' +export type * from './types' +export * from './utils' +export { + and, + arrayContained, + arrayContains, + arrayOverlaps, + asc, + between, + desc, + eq, + exists, + gt, + gte, + ilike, + inArray, + isNotNull, + isNull, + like, + lt, + lte, + ne, + not, + notBetween, + notExists, + notIlike, + notInArray, + notLike, + or +} from '@fisch0920/drizzle-orm' +export { default as postgres } from 'postgres' diff --git a/apps/api/src/db/schema/utils.ts b/packages/db/src/schema/common.ts similarity index 59% rename from apps/api/src/db/schema/utils.ts rename to packages/db/src/schema/common.ts index 0eb00045..e0da409a 100644 --- a/apps/api/src/db/schema/utils.ts +++ b/packages/db/src/schema/common.ts @@ -1,4 +1,3 @@ -import { hashObject } from '@agentic/platform-core' import { type Equal, sql, type Writable } from '@fisch0920/drizzle-orm' import { pgEnum, @@ -14,14 +13,6 @@ import { createSchemaFactory } from '@fisch0920/drizzle-zod' import { z } from '@hono/zod-openapi' import { createId } from '@paralleldrive/cuid2' -import type { RawProject } from '../types' -import type { - PricingInterval, - PricingPlan, - PricingPlanLineItem, - PricingPlanList -} from './schemas' - const usernameAndTeamSlugLength = 64 as const /** @@ -125,85 +116,3 @@ export const { createInsertSchema, createSelectSchema, createUpdateSchema } = date: true } }) - -/** - * Gets the hash used to uniquely map a PricingPlanLineItem to its - * corresponding Stripe Price in a stable way across deployments within a - * project. - * - * This hash is used as the key for the `Project._stripePriceIdMap`. - */ -export function getPricingPlanLineItemHashForStripePrice({ - pricingPlan, - pricingPlanLineItem, - project -}: { - pricingPlan: PricingPlan - pricingPlanLineItem: PricingPlanLineItem - project: RawProject -}) { - // TODO: use pricingPlan.slug as well here? - // TODO: not sure if this is needed or not... - // With pricing plan slug: - // - 'price:free:base:' - // - 'price:basic-monthly:base:' - // - 'price:basic-monthly:requests:' - // Without pricing plan slug: - // - 'price:base:' - // - 'price:base:' - // - 'price:requests:' - - const hash = hashObject({ - ...pricingPlanLineItem, - projectId: project.id, - stripeAccountId: project._stripeAccountId, - currency: project.pricingCurrency - }) - - return `price:${pricingPlan.slug}:${pricingPlanLineItem.slug}:${hash}` -} - -export function getStripePriceIdForPricingPlanLineItem({ - pricingPlan, - pricingPlanLineItem, - project -}: { - pricingPlan: PricingPlan - pricingPlanLineItem: PricingPlanLineItem - project: RawProject -}): string | undefined { - const pricingPlanLineItemHash = getPricingPlanLineItemHashForStripePrice({ - pricingPlan, - pricingPlanLineItem, - project - }) - - return project._stripePriceIdMap[pricingPlanLineItemHash] -} - -export function getPricingPlansByInterval({ - pricingInterval, - pricingPlans -}: { - pricingInterval: PricingInterval - pricingPlans: PricingPlanList -}): PricingPlan[] { - return pricingPlans.filter( - (pricingPlan) => - pricingPlan.interval === undefined || - pricingPlan.interval === pricingInterval - ) -} - -const pricingIntervalToLabelMap: Record = { - day: 'daily', - week: 'weekly', - month: 'monthly', - year: 'yearly' -} - -export function getLabelForPricingInterval( - pricingInterval: PricingInterval -): string { - return pricingIntervalToLabelMap[pricingInterval] -} diff --git a/apps/api/src/db/schema/consumer.ts b/packages/db/src/schema/consumer.ts similarity index 94% rename from apps/api/src/db/schema/consumer.ts rename to packages/db/src/schema/consumer.ts index c660e246..787b6051 100644 --- a/apps/api/src/db/schema/consumer.ts +++ b/packages/db/src/schema/consumer.ts @@ -1,4 +1,4 @@ -import { validators } from '@agentic/validators' +import { validators } from '@agentic/platform-validators' import { relations } from '@fisch0920/drizzle-orm' import { boolean, @@ -9,13 +9,6 @@ import { } from '@fisch0920/drizzle-orm/pg-core' import { z } from '@hono/zod-openapi' -import { deployments, deploymentSelectSchema } from './deployment' -import { projects, projectSelectSchema } from './project' -import { - type StripeSubscriptionItemIdMap, - stripeSubscriptionItemIdMapSchema -} from './schemas' -import { users, userSelectSchema } from './user' import { createInsertSchema, createSelectSchema, @@ -26,7 +19,14 @@ import { projectId, stripeId, timestamps -} from './utils' +} from './common' +import { deployments, deploymentSelectSchema } from './deployment' +import { projects, projectSelectSchema } from './project' +import { + type StripeSubscriptionItemIdMap, + stripeSubscriptionItemIdMapSchema +} from './schemas' +import { users, userSelectSchema } from './user' // TODO: Consumers should be valid for any enabled project like in RapidAPI and GCP. // This may require a separate model to aggregate User Applications. @@ -128,13 +128,6 @@ export const consumersRelations = relations(consumers, ({ one }) => ({ }) })) -export type ConsumerRelationFields = keyof ReturnType< - (typeof consumersRelations)['config'] -> - -export const consumerRelationsSchema: z.ZodType = - z.enum(['user', 'project', 'deployment']) - export const consumerSelectSchema = createSelectSchema(consumers, { _stripeSubscriptionItemIdMap: stripeSubscriptionItemIdMapSchema, diff --git a/apps/api/src/db/schema/deployment.ts b/packages/db/src/schema/deployment.ts similarity index 95% rename from apps/api/src/db/schema/deployment.ts rename to packages/db/src/schema/deployment.ts index d208ba7b..3ad0c165 100644 --- a/apps/api/src/db/schema/deployment.ts +++ b/packages/db/src/schema/deployment.ts @@ -1,4 +1,4 @@ -import { validators } from '@agentic/validators' +import { validators } from '@agentic/platform-validators' import { relations } from '@fisch0920/drizzle-orm' import { boolean, @@ -9,6 +9,15 @@ import { } from '@fisch0920/drizzle-orm/pg-core' import { z } from '@hono/zod-openapi' +import { + createInsertSchema, + createSelectSchema, + createUpdateSchema, + cuid, + deploymentId, + projectId, + timestamps +} from './common' import { projects } from './project' import { type DeploymentOriginAdapter, @@ -18,15 +27,6 @@ import { } from './schemas' import { teams, teamSelectSchema } from './team' import { users, userSelectSchema } from './user' -import { - createInsertSchema, - createSelectSchema, - createUpdateSchema, - cuid, - deploymentId, - projectId, - timestamps -} from './utils' export const deployments = pgTable( 'deployments', @@ -105,13 +105,6 @@ export const deploymentsRelations = relations(deployments, ({ one }) => ({ }) })) -export type DeploymentRelationFields = keyof ReturnType< - (typeof deploymentsRelations)['config'] -> - -export const deploymentRelationsSchema: z.ZodType = - z.enum(['user', 'team', 'project']) - // TODO: virtual hasFreeTier // TODO: virtual url // TODO: virtual openApiUrl diff --git a/apps/api/src/db/schema/index.ts b/packages/db/src/schema/index.ts similarity index 78% rename from apps/api/src/db/schema/index.ts rename to packages/db/src/schema/index.ts index 44dcc605..ebf2cec2 100644 --- a/apps/api/src/db/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -1,9 +1,8 @@ +export * from './common' export * from './consumer' export * from './deployment' export * from './log-entry' export * from './project' -export * from './schemas' export * from './team' export * from './team-member' export * from './user' -export * from './utils' diff --git a/apps/api/src/db/schema/log-entry.ts b/packages/db/src/schema/log-entry.ts similarity index 99% rename from apps/api/src/db/schema/log-entry.ts rename to packages/db/src/schema/log-entry.ts index b2ea82e1..35311b37 100644 --- a/apps/api/src/db/schema/log-entry.ts +++ b/packages/db/src/schema/log-entry.ts @@ -2,10 +2,6 @@ import { relations } from '@fisch0920/drizzle-orm' import { index, jsonb, pgTable, text } from '@fisch0920/drizzle-orm/pg-core' import { z } from '@hono/zod-openapi' -import { consumers, consumerSelectSchema } from './consumer' -import { deployments, deploymentSelectSchema } from './deployment' -import { projects, projectSelectSchema } from './project' -import { users, userSelectSchema } from './user' import { createInsertSchema, createSelectSchema, @@ -16,7 +12,11 @@ import { logEntryTypeEnum, projectId, timestamps -} from './utils' +} from './common' +import { consumers, consumerSelectSchema } from './consumer' +import { deployments, deploymentSelectSchema } from './deployment' +import { projects, projectSelectSchema } from './project' +import { users, userSelectSchema } from './user' /** * A `LogEntry` is an internal audit log entry. diff --git a/apps/api/src/db/schema/project.ts b/packages/db/src/schema/project.ts similarity index 95% rename from apps/api/src/db/schema/project.ts rename to packages/db/src/schema/project.ts index d8e2f397..947f2181 100644 --- a/apps/api/src/db/schema/project.ts +++ b/packages/db/src/schema/project.ts @@ -1,4 +1,4 @@ -import { validators } from '@agentic/validators' +import { validators } from '@agentic/platform-validators' import { relations } from '@fisch0920/drizzle-orm' import { boolean, @@ -10,6 +10,18 @@ import { } from '@fisch0920/drizzle-orm/pg-core' import { z } from '@hono/zod-openapi' +import { + createInsertSchema, + createSelectSchema, + createUpdateSchema, + cuid, + deploymentId, + pricingCurrencyEnum, + pricingIntervalEnum, + projectId, + stripeId, + timestamps +} from './common' import { deployments, deploymentSelectSchema } from './deployment' import { pricingIntervalSchema, @@ -22,18 +34,6 @@ import { } from './schemas' import { teams, teamSelectSchema } from './team' import { users, userSelectSchema } from './user' -import { - createInsertSchema, - createSelectSchema, - createUpdateSchema, - cuid, - deploymentId, - pricingCurrencyEnum, - pricingIntervalEnum, - projectId, - stripeId, - timestamps -} from './utils' export const projects = pgTable( 'projects', @@ -157,17 +157,6 @@ export const projectsRelations = relations(projects, ({ one }) => ({ // }) })) -export type ProjectRelationFields = keyof ReturnType< - (typeof projectsRelations)['config'] -> - -export const projectRelationsSchema: z.ZodType = z.enum([ - 'user', - 'team', - 'lastPublishedDeployment', - 'lastDeployment' -]) - export const projectSelectSchema = createSelectSchema(projects, { applicationFeePercent: (schema) => schema.nonnegative(), diff --git a/apps/api/src/db/schema/schemas.ts b/packages/db/src/schema/schemas.ts similarity index 100% rename from apps/api/src/db/schema/schemas.ts rename to packages/db/src/schema/schemas.ts diff --git a/apps/api/src/db/schema/team-member.ts b/packages/db/src/schema/team-member.ts similarity index 99% rename from apps/api/src/db/schema/team-member.ts rename to packages/db/src/schema/team-member.ts index 246684b2..1a065254 100644 --- a/apps/api/src/db/schema/team-member.ts +++ b/packages/db/src/schema/team-member.ts @@ -7,8 +7,6 @@ import { } from '@fisch0920/drizzle-orm/pg-core' import { z } from '@hono/zod-openapi' -import { teams, teamSelectSchema } from './team' -import { users, userSelectSchema } from './user' import { createInsertSchema, createSelectSchema, @@ -18,7 +16,9 @@ import { teamSlug, timestamp, timestamps -} from './utils' +} from './common' +import { teams, teamSelectSchema } from './team' +import { users, userSelectSchema } from './user' export const teamMembers = pgTable( 'team_members', diff --git a/apps/api/src/db/schema/team.ts b/packages/db/src/schema/team.ts similarity index 95% rename from apps/api/src/db/schema/team.ts rename to packages/db/src/schema/team.ts index 78e9098b..fd6f8aa8 100644 --- a/apps/api/src/db/schema/team.ts +++ b/packages/db/src/schema/team.ts @@ -1,4 +1,4 @@ -import { validators } from '@agentic/validators' +import { validators } from '@agentic/platform-validators' import { relations } from '@fisch0920/drizzle-orm' import { index, @@ -8,8 +8,6 @@ import { } from '@fisch0920/drizzle-orm/pg-core' import { z } from '@hono/zod-openapi' -import { teamMembers } from './team-member' -import { users, userSelectSchema } from './user' import { createInsertSchema, createSelectSchema, @@ -18,7 +16,9 @@ import { id, teamSlug, timestamps -} from './utils' +} from './common' +import { teamMembers } from './team-member' +import { users, userSelectSchema } from './user' export const teams = pgTable( 'teams', diff --git a/apps/api/src/db/schema/temp b/packages/db/src/schema/temp similarity index 100% rename from apps/api/src/db/schema/temp rename to packages/db/src/schema/temp diff --git a/apps/api/src/db/schema/user.ts b/packages/db/src/schema/user.ts similarity index 97% rename from apps/api/src/db/schema/user.ts rename to packages/db/src/schema/user.ts index 50c8bf77..4eeffb82 100644 --- a/apps/api/src/db/schema/user.ts +++ b/packages/db/src/schema/user.ts @@ -1,5 +1,5 @@ import { sha256 } from '@agentic/platform-core' -import { validators } from '@agentic/validators' +import { validators } from '@agentic/platform-validators' import { relations } from '@fisch0920/drizzle-orm' import { boolean, @@ -11,8 +11,6 @@ import { } from '@fisch0920/drizzle-orm/pg-core' import { hashSync } from 'bcryptjs' -import { type AuthProviders, publicAuthProvidersSchema } from './schemas' -import { teams } from './team' import { createInsertSchema, createSelectSchema, @@ -23,7 +21,9 @@ import { timestamps, username, userRoleEnum -} from './utils' +} from './common' +import { type AuthProviders, publicAuthProvidersSchema } from './schemas' +import { teams } from './team' export const users = pgTable( 'users', diff --git a/apps/api/src/db/schemas.ts b/packages/db/src/schemas.ts similarity index 64% rename from apps/api/src/db/schemas.ts rename to packages/db/src/schemas.ts index e4bab952..151cec49 100644 --- a/apps/api/src/db/schemas.ts +++ b/packages/db/src/schemas.ts @@ -1,6 +1,10 @@ -import { validators } from '@agentic/validators' +import { validators } from '@agentic/platform-validators' import { z } from '@hono/zod-openapi' +import type { consumersRelations } from './schema/consumer' +import type { deploymentsRelations } from './schema/deployment' +import type { projectsRelations } from './schema/project' + function getCuidSchema(idLabel: string) { return z.string().refine((id) => validators.cuid(id), { message: `Invalid ${idLabel}` @@ -57,3 +61,25 @@ export const paginationSchema = z.object({ // ) // }) // } + +export type ProjectRelationFields = keyof ReturnType< + (typeof projectsRelations)['config'] +> +export const projectRelationsSchema: z.ZodType = z.enum([ + 'user', + 'team', + 'lastPublishedDeployment', + 'lastDeployment' +]) + +export type DeploymentRelationFields = keyof ReturnType< + (typeof deploymentsRelations)['config'] +> +export const deploymentRelationsSchema: z.ZodType = + z.enum(['user', 'team', 'project']) + +export type ConsumerRelationFields = keyof ReturnType< + (typeof consumersRelations)['config'] +> +export const consumerRelationsSchema: z.ZodType = + z.enum(['user', 'project', 'deployment']) diff --git a/apps/api/src/db/types.test.ts b/packages/db/src/types.test.ts similarity index 84% rename from apps/api/src/db/types.test.ts rename to packages/db/src/types.test.ts index 1fcd86c5..857cb1df 100644 --- a/apps/api/src/db/types.test.ts +++ b/packages/db/src/types.test.ts @@ -1,7 +1,5 @@ import { expectTypeOf, test } from 'vitest' -import type { LogLevel } from '@/lib/logger' - import type { LogEntry, RawLogEntry, RawUser, User } from './types' type UserKeys = Exclude @@ -19,6 +17,4 @@ test('LogEntry types are compatible', () => { expectTypeOf().toEqualTypeOf< RawLogEntry[LogEntryKeys] >() - - expectTypeOf().toEqualTypeOf() }) diff --git a/apps/api/src/db/types.ts b/packages/db/src/types.ts similarity index 100% rename from apps/api/src/db/types.ts rename to packages/db/src/types.ts diff --git a/packages/db/src/utils.ts b/packages/db/src/utils.ts new file mode 100644 index 00000000..09d777e5 --- /dev/null +++ b/packages/db/src/utils.ts @@ -0,0 +1,91 @@ +import { hashObject } from '@agentic/platform-core' + +import type { + PricingInterval, + PricingPlan, + PricingPlanLineItem, + PricingPlanList +} from './schema/schemas' +import type { RawProject } from './types' + +/** + * Gets the hash used to uniquely map a PricingPlanLineItem to its + * corresponding Stripe Price in a stable way across deployments within a + * project. + * + * This hash is used as the key for the `Project._stripePriceIdMap`. + */ +export function getPricingPlanLineItemHashForStripePrice({ + pricingPlan, + pricingPlanLineItem, + project +}: { + pricingPlan: PricingPlan + pricingPlanLineItem: PricingPlanLineItem + project: RawProject +}) { + // TODO: use pricingPlan.slug as well here? + // TODO: not sure if this is needed or not... + // With pricing plan slug: + // - 'price:free:base:' + // - 'price:basic-monthly:base:' + // - 'price:basic-monthly:requests:' + // Without pricing plan slug: + // - 'price:base:' + // - 'price:base:' + // - 'price:requests:' + + const hash = hashObject({ + ...pricingPlanLineItem, + projectId: project.id, + stripeAccountId: project._stripeAccountId, + currency: project.pricingCurrency + }) + + return `price:${pricingPlan.slug}:${pricingPlanLineItem.slug}:${hash}` +} + +export function getStripePriceIdForPricingPlanLineItem({ + pricingPlan, + pricingPlanLineItem, + project +}: { + pricingPlan: PricingPlan + pricingPlanLineItem: PricingPlanLineItem + project: RawProject +}): string | undefined { + const pricingPlanLineItemHash = getPricingPlanLineItemHashForStripePrice({ + pricingPlan, + pricingPlanLineItem, + project + }) + + return project._stripePriceIdMap[pricingPlanLineItemHash] +} + +export function getPricingPlansByInterval({ + pricingInterval, + pricingPlans +}: { + pricingInterval: PricingInterval + pricingPlans: PricingPlanList +}): PricingPlan[] { + return pricingPlans.filter( + (pricingPlan) => + pricingPlan.interval === undefined || + pricingPlan.interval === pricingInterval + ) +} + +const pricingIntervalToLabelMap: Record = { + day: 'daily', + week: 'weekly', + month: 'monthly', + year: 'yearly' +} + +export function getLabelForPricingInterval( + pricingInterval: PricingInterval +): string { + return pricingIntervalToLabelMap[pricingInterval] +} diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json new file mode 100644 index 00000000..ce6d4e23 --- /dev/null +++ b/packages/db/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@fisch0920/config/tsconfig-node", + "include": ["src", "*.config.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/validators/package.json b/packages/validators/package.json index 35df8e7c..81051e18 100644 --- a/packages/validators/package.json +++ b/packages/validators/package.json @@ -1,5 +1,5 @@ { - "name": "@agentic/validators", + "name": "@agentic/platform-validators", "private": true, "version": "0.1.0", "description": "Validation utils for the Agentic platform.", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2f5b844..94dc4718 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -128,15 +128,12 @@ importers: '@agentic/platform-core': specifier: workspace:* version: link:../../packages/core - '@agentic/validators': + '@agentic/platform-db': + specifier: workspace:* + version: link:../../packages/db + '@agentic/platform-validators': specifier: workspace:* version: link:../../packages/validators - '@fisch0920/drizzle-orm': - specifier: ^0.43.7 - version: 0.43.7(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(postgres@3.4.5) - '@fisch0920/drizzle-zod': - specifier: ^0.7.9 - version: 0.7.9(@fisch0920/drizzle-orm@0.43.7(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(postgres@3.4.5))(zod@3.24.4) '@hono/node-server': specifier: ^1.14.1 version: 1.14.1(hono@4.7.9) @@ -146,21 +143,12 @@ importers: '@hono/zod-openapi': specifier: ^0.19.6 version: 0.19.6(hono@4.7.9)(zod@3.24.4) - '@hono/zod-validator': - specifier: ^0.5.0 - version: 0.5.0(hono@4.7.9)(zod@3.24.4) - '@paralleldrive/cuid2': - specifier: ^2.2.2 - version: 2.2.2 '@redocly/openapi-core': specifier: ^1.34.3 version: 1.34.3(supports-color@10.0.0) '@sentry/node': specifier: ^9.19.0 version: 9.19.0 - bcryptjs: - specifier: ^3.0.2 - version: 3.0.2 eventid: specifier: ^2.0.1 version: 2.0.1 @@ -179,9 +167,6 @@ importers: parse-json: specifier: ^8.3.0 version: 8.3.0 - postgres: - specifier: ^3.4.5 - version: 3.4.5 restore-cursor: specifier: 'catalog:' version: 5.1.0 @@ -238,6 +223,9 @@ importers: '@agentic/platform-api': specifier: workspace:* version: link:../../apps/api + '@agentic/platform-db': + specifier: workspace:* + version: link:../db '@commander-js/extra-typings': specifier: ^14.0.0 version: 14.0.0(commander@14.0.0) @@ -258,6 +246,45 @@ importers: specifier: ^4.7.9 version: 4.7.9 + packages/db: + dependencies: + '@agentic/platform-core': + specifier: workspace:* + version: link:../core + '@agentic/platform-validators': + specifier: workspace:* + version: link:../validators + '@fisch0920/drizzle-orm': + specifier: ^0.43.7 + version: 0.43.7(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(postgres@3.4.5) + '@fisch0920/drizzle-zod': + specifier: ^0.7.9 + version: 0.7.9(@fisch0920/drizzle-orm@0.43.7(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(postgres@3.4.5))(zod@3.24.4) + '@hono/zod-openapi': + specifier: ^0.19.6 + version: 0.19.6(hono@4.7.9)(zod@3.24.4) + '@paralleldrive/cuid2': + specifier: ^2.2.2 + version: 2.2.2 + bcryptjs: + specifier: ^3.0.2 + version: 3.0.2 + hono: + specifier: ^4.7.9 + version: 4.7.9 + parse-json: + specifier: ^8.3.0 + version: 8.3.0 + postgres: + specifier: ^3.4.5 + version: 3.4.5 + type-fest: + specifier: 'catalog:' + version: 4.41.0 + zod: + specifier: 'catalog:' + version: 3.24.4 + packages/validators: dependencies: '@paralleldrive/cuid2':