From 219fd2274cbf71aeeb3744810c3a26cbbc880b3f Mon Sep 17 00:00:00 2001 From: Daniel Salazar Date: Thu, 23 Oct 2025 13:57:32 -0700 Subject: [PATCH] feat: allow for unlimitedAllowList + typedefs snuck in by accident (#1813) * feat: allow for unlimitedAllowList * feat: add clearer types to extensions --- extensions/api.d.ts | 70 +++++++++++-------- extensions/metering/config.json | 3 + .../eventListeners/subscriptionEvents.js | 12 +++- extensions/metering/routes/usage.js | 1 - tsconfig.json | 1 - 5 files changed, 52 insertions(+), 35 deletions(-) diff --git a/extensions/api.d.ts b/extensions/api.d.ts index 2c75c16ee..48024d90e 100644 --- a/extensions/api.d.ts +++ b/extensions/api.d.ts @@ -1,51 +1,61 @@ +import type { Actor } from '@heyputer/backend/src/services/auth/Actor.js'; +import type { MeteringService } from '@heyputer/backend/src/services/MeteringService/MeteringService.ts'; +import type { DBKVStore } from '@heyputer/backend/src/services/repositories/DBKVStore/DBKVStore.ts'; +import type { SUService } from '@heyputer/backend/src/services/SUService.js'; import type { RequestHandler } from 'express'; import type helpers from '../src/backend/src/helpers.js'; declare global { - namespace Express { - interface Request { - actor: any; // TODO + namespace Express { + interface Request { + services: { get: (string: T)=> T extends keyof ServiceNameMap ? ServiceNameMap[T] : unknown } + actor: Actor + } } - } } -type EndpointOptions = { - allowedMethods?: string[] - subdomain?: string - noauth?: boolean +interface EndpointOptions { + allowedMethods?: string[] + subdomain?: string + noauth?: boolean } type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch'; -type AddRouteFunction = (path: string, options: EndpointOptions, handler: RequestHandler) => void +export type AddRouteFunction = (path: string, options: EndpointOptions, handler: RequestHandler) => void; type RouterMethods = { - [K in HttpMethod]: { - (path: string, options: EndpointOptions, handler: RequestHandler): void; - (path: string, handler: RequestHandler, options?: EndpointOptions): void; - }; + [K in HttpMethod]: { + (path: string, options: EndpointOptions, handler: RequestHandler): void; + (path: string, handler: RequestHandler, options?: EndpointOptions): void; + }; +}; + +interface CoreRuntimeModule { + util: { + helpers: typeof helpers, + } } -type CoreRuntimeModule = { - util: { - helpers: typeof helpers, - } -} +type StripPrefix = T extends `${TPrefix}.${infer R}` ? R : never; +// TODO DS: define this globally in core to use it there too +interface ServiceNameMap { + 'meteringService': { meteringService: MeteringService } & MeteringService // TODO DS: squash into a single class without wrapper + 'puter-kv': DBKVStore + 'su': SUService +} interface Extension extends RouterMethods { - // import(module: 'core'): { - // UserActorType: typeof UserActorType; - // }; - import(module: 'core'): CoreRuntimeModule; - import(module: string): any; + import(module: T): T extends `service:${infer R extends keyof ServiceNameMap}` + ? ServiceNameMap[R] + : T extends 'core' + ? CoreRuntimeModule + : unknown; } declare global { - // Declare the extension variable - const extension: Extension; - const config: Record; - const global_config: Record; + // Declare the extension variable + const extension: Extension; + const config: Record; + const global_config: Record; } - -export { }; - diff --git a/extensions/metering/config.json b/extensions/metering/config.json index 2285e3803..2c8baa8bc 100644 --- a/extensions/metering/config.json +++ b/extensions/metering/config.json @@ -1,5 +1,8 @@ { "unlimitedUsage": false, + "unlimitedAllowList": [ + "admin" + ], "allowedGlobalUsageUsers": [ "06ab2f87-aef5-441b-9c60-debbb8d24dda", "d8fd169b-4e93-484a-bd84-115b5a2f0ed4" diff --git a/extensions/metering/eventListeners/subscriptionEvents.js b/extensions/metering/eventListeners/subscriptionEvents.js index 3552b2dc8..68148063d 100644 --- a/extensions/metering/eventListeners/subscriptionEvents.js +++ b/extensions/metering/eventListeners/subscriptionEvents.js @@ -9,8 +9,7 @@ extension.on('metering:overrideDefaultSubscription', async (/** @type {{actor: i extension.on('metering:registerAvailablePolicies', async ( /** @type {{actor: import('@heyputer/backend/src/services/auth/Actor').Actor, availablePolicies: unknown[]}} */event) => { // bit of a stub implementation for OSS, technically can be always free if you set this config true - if ( config.unlimitedUsage ) { - console.warn('WARNING!!! unlimitedUsage is enabled, this is not recommended for production use'); + if ( config.unlimitedUsage || config.unlimitedAllowList?.length ) { event.availablePolicies.push({ id: 'unlimited', monthUsageAllowance: 5_000_000 * 1_000_000 * 100, // unless you're like, jeff's, mark's, and elon's illegitamate son, you probably won't hit $5m a month @@ -20,6 +19,13 @@ extension.on('metering:registerAvailablePolicies', async ( }); extension.on('metering:getUserSubscription', async (/** @type {{actor: import('@heyputer/backend/src/services/auth/Actor').Actor, userSubscriptionId: string}} */event) => { - event.userSubscriptionId = event?.actor?.type?.user?.subscription?.active ? event.actor.type.user.subscription?.tier : undefined; + const userName = event?.actor?.type?.user?.username; + if ( config.unlimitedAllowList?.includes(userName) ) { + console.warn(`WARNING!!! User ${userName} is on unlimited usage allow list, this is not recommended for production use`); + event.userSubscriptionId; + } + else { + event.userSubscriptionId = event?.actor?.type?.user?.subscription?.active ? event.actor.type.user.subscription?.tier : undefined; + } // default location for user sub, but can techinically be anywhere else or fetched on request }); diff --git a/extensions/metering/routes/usage.js b/extensions/metering/routes/usage.js index f3a8a6c4c..32d731af2 100644 --- a/extensions/metering/routes/usage.js +++ b/extensions/metering/routes/usage.js @@ -1,4 +1,3 @@ -/** @type {import('@heyputer/backend/src/services/MeteringService/MeteringServiceWrapper.mjs').MeteringServiceWrapper} */ const meteringServiceWrapper = extension.import('service:meteringService'); // TODO DS: move this to its own router and just use under this path diff --git a/tsconfig.json b/tsconfig.json index 8b0703bd7..4ad21f1fe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,5 @@ "**/tests/**", "node_modules", "dist", - "extensions" ] } \ No newline at end of file