@meyicloud/meyi-cost-server
v1.8.6
Published
Tenant-aware AWS CUR and Athena cost plugin server for MeyiConnect
Keywords
Readme
Meyi Insight Cost Server
Tenant-aware Express plugin that provides AWS cost overview, reports, dynamic filters, budgets, and budget-alert dismissals from centralized Cost and Usage Report (CUR) data queried through Athena. CUR is mandatory; the package does not call or fall back to AWS Cost Explorer.
The package is designed to be mounted by a host backend. The host owns login, token verification, tenant/plugin enablement, PostgreSQL connection creation, and AWS onboarding. The plugin owns cost routes and its budget-related tables.
Package contract
- Package name:
@meyicloud/meyi-cost-server - Runtime: Node.js 20 or newer, ESM
- Framework: Express 4
- Database API: injected Drizzle PostgreSQL instance
- Peer dependency:
pg >= 8 - Default route:
/api/v1/cost - Public factory:
createInsightCost({ app, db, apiBaseUri, logger })
Features
- Tenant-isolated payer/organization and member-account cost views
- Monthly and yearly overview data
- Service, account, region, resource, and cost-allocation-tag reports
- Dynamic service, region, linked-account, and tag filter options
- Standard and comparison report data used by
@meyicloud/meyi-cost-ui - CUR/Athena active-resource counts and detailed cost data
- Explicit CUR setup, pending-data, and unavailable states
- Tenant-scoped budget rules
- User-, tenant-, month-, and status-scoped budget-alert dismissals
- Tenant-configurable daily, weekly, and monthly AI report schedules
- PostgreSQL-backed background report jobs with failure history
- Analyser-generated Markdown and PDF reports available through tenant-scoped downloads
Budgets are application rules stored in PostgreSQL; they are not AWS Budgets resources. The consuming application evaluates them against current cost and shows the notification UI.
Architecture and request flow
flowchart LR
Host[External Express application] --> Auth[Authentication]
Auth --> Enabled[Tenant plugin check]
Enabled --> Router[/api/v1/cost]
Router --> Controller[Cost controller]
Controller --> Customer[Tenant CUR metadata]
Controller --> SaaS[SaaS Athena context]
SaaS --> Athena[Central tenant and connection-partitioned CUR]
Controller --> Budget[Budget services]
Budget --> DB[(PostgreSQL)]src/
|-- controllers/ HTTP-to-service orchestration
|-- routes/ Express routes and error handling
|-- models/ Normalized immutable domain models
|-- repositories/ Tenant onboarding persistence access
|-- services/ AWS CUR/Athena contexts and budgets
|-- schema/ Budget and dismissal table installation
|-- lib/ Cost, date, filter, and SQL helpers
`-- plugin.js Dependency composition and lifecycleLocal development and validation
Requirements:
- Node.js 20 or newer
- npm
- PostgreSQL available to the host used for integration testing
Install dependencies:
npm installThis package is native JavaScript ESM and has no transpilation build step. Validate syntax and package contents with:
node --check index.js
node --check cur.js
node --check src/plugin.js
npm pack --dry-runWhen files below src change, syntax-check every changed .js file. The npm
archive must include index.js, cur.js, the complete src directory,
README.md, and AGENTS.md.
For a local package installation test:
npm pack
npm install /path/to/meyicloud-meyi-cost-server-1.6.0.tgzPublish to npm
Publishing changes the external registry. Run these commands only after release authorization:
npm login
npm whoami
npm version patch
npm pack --dry-run
npm publish --access publicUse npm version minor or npm version major when appropriate. This server
package currently has no compile step or prepublishOnly script, so syntax and
integration validation must be completed before publishing.
After publishing:
npm view @meyicloud/meyi-cost-server version
npm install @meyicloud/meyi-cost-server@<published-version>Integrate with an external Express application
Install the package and its PostgreSQL peer dependency:
npm install @meyicloud/meyi-cost-server pgFor local development, use a file dependency:
{
"dependencies": {
"@meyicloud/meyi-cost-server": "file:../../meyi-market-places/insight-cost-server"
}
}Create the plugin using the host's Express app and Drizzle database:
import express from "express";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { createInsightCost } from "@meyicloud/meyi-cost-server";
const app = express();
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool);
app.use(express.json());
// These host middlewares must be registered before plugin.start().
app.use("/api/v1/cost", authenticateRequest);
app.use("/api/v1/cost", requireCostPluginForTenant);
const costPlugin = createInsightCost({
app,
db,
apiBaseUri: "/api/v1",
logger: console,
});
await costPlugin.install();
await costPlugin.start();Lifecycle behavior:
install()creates or verifies plugin-owned budget and dismissal tables.start()mounts the router once at${apiBaseUri}/cost.stop()is currently a no-op but is available for host lifecycle symmetry.routeris also returned for hosts that need custom mounting.
app is optional only when the host mounts the returned router itself. db
is required.
Host authentication and tenant contract
Before a request reaches the plugin, the host should authenticate it and set:
req.user = {
id: "authenticated-user-id",
tenant_id: "authenticated-tenant-id",
};The tenant resolver checks req.user.tenant_id, then req.user.tenantId, then
x-tenant-id, then DEFAULT_TENANT_ID. In production, prefer an authenticated
req.user tenant and do not accept an untrusted tenant header.
The Marketplace onboarding integration loads tenant access from:
<DB_SCHEMA>.aws_connections<DB_SCHEMA>.aws_accounts<DB_SCHEMA>.cost_cur_config<DB_SCHEMA>.cost_cur_discovery_jobs<DB_SCHEMA>.cost_cur_discovery_job_logs
Only a connected row containing Cost with verified Cost access is accepted.
Connection metadata supplies account display names and CUR source metadata; the
dedicated Cost CUR table supplies the tenant partition and delivery/readiness
state. Customer credentials are never passed to the central Athena client.
CUR discovery runs as one shared scheduler with independent tenant job rows.
Each job checks only its configured S3 prefix, finds a tenant-safe Glue table,
and verifies the tenant partition through Athena. A successful job becomes
READY and is no longer scheduled. Job events are retained separately in
cost_cur_discovery_job_logs and also include tenant and job IDs in application
logs.
Data-source behavior
Central CUR through SaaS Athena
CUR applies line_item_unblended_cost > 0 for displayed gross cost and can also
calculate net cost, credits/adjustments, active resources, detailed resource
rows, account/region relationships, and allocation-tag data.
The SaaS runtime identity, or the dedicated role configured by
COST_CUR_ROLE_ARN, queries the SaaS-owned Athena/Glue/S3 resources. Customer
onboarding creates a CUR 2.0 Data Export directly into a tenant prefix in the
Meyi-owned bucket; customer AWS credentials are not reused for Athena.
CUR is mandatory. /data-status reports whether configuration, discovery, and
tenant rows are ready. Data endpoints return HTTP 503 with a structured state
and action when CUR is not configured, still waiting for rows, or unavailable.
Environment variables
All variables below belong to the backend runtime. Never place AWS or
database secrets in @meyicloud/meyi-cost-ui, a browser environment, source control, or a
Docker image layer. Supply them at container/process runtime.
Database and tenant variables
| Variable | Default | Required | Purpose |
| --- | --- | --- | --- |
| DB_SCHEMA | meyiconnect | No | PostgreSQL schema containing onboarding and plugin-owned tables. |
| DEFAULT_TENANT_ID | default | No | Final tenant fallback when authenticated request context is absent. Prefer authenticated tenant context in production. |
| DATABASE_URL | None | Host-specific | Not read by the package directly. The host commonly uses it to create the injected db connection. |
Production tenants should use per-tenant onboarding role metadata. Environment credentials are process-wide and therefore unsuitable as the normal credential source for a multi-tenant service.
When customer environment credentials are enabled, central Athena is disabled
unless COST_CUR_ROLE_ARN is also configured. This prevents the AWS SDK default
credential chain from accidentally using a customer's local test key against
SaaS Athena resources.
AI analysis variables
AI reports are generated by a separate meyi-cost-ai-analyser Fargate task.
The backend only schedules jobs, launches and monitors the task, and serves
tenant-scoped artifacts. It does not invoke Bedrock or generate PDFs itself.
Daily reports cover the previous complete day, weekly reports cover the
previous seven complete days, and monthly reports cover the previous calendar
month in the configured timezone.
The authenticated report API is:
| Method | Route | Purpose |
| --- | --- | --- |
| GET | /analysis/schedule | Read this tenant's schedule. |
| PUT | /analysis/schedule | Update the schedule; requires req.user.role=admin. |
| GET | /analysis/reports | List tenant report history without PDF bytes. |
| GET | /analysis/reports/:id | Read one tenant report. |
| GET | /analysis/reports/:id/pdf | Download a completed report PDF. |
| GET | /analysis/reports/:id/markdown | Download the canonical Markdown report. |
Schedule and report rows live in cost_ai_report_schedules and
cost_ai_reports. The worker atomically claims due schedules with a lease, so
a restarted task can resume future work without relying on in-memory timers.
| Variable | Default | Required | Purpose |
| --- | --- | --- | --- |
| COST_AI_ENABLED | false | No | Enables the schedule worker and external analyser integration. |
| COST_AI_ANALYSER_REGION | AWS_REGION | When enabled | ECS and report-bucket region. |
| COST_AI_ANALYSER_CLUSTER | Empty | When enabled | ECS cluster ARN or name. |
| COST_AI_ANALYSER_TASK_DEFINITION | Empty | When enabled | Fargate task definition ARN. |
| COST_AI_ANALYSER_CONTAINER_NAME | cost-ai-analyser | No | Container override target. |
| COST_AI_ANALYSER_SUBNETS | Empty | When enabled | Comma-separated private subnet IDs. |
| COST_AI_ANALYSER_SECURITY_GROUPS | Empty | When enabled | Comma-separated task security groups. |
| COST_AI_ANALYSER_REPORT_BUCKET | Empty | When enabled | Private S3 Markdown/PDF artifact bucket. |
| COST_AI_ANALYSER_PROVIDER_SECRET_PREFIX | Empty | When enabled | Prefix for temporary Secrets Manager entries containing each tenant's active Meyi Connect AI provider configuration. |
| COST_AI_ANALYSER_TARGET_REGIONS | AWS_REGION | No | Customer regions inspected for resource detail. |
| COST_AI_ANALYSER_TOP_N_SERVICES | 10 | No | Maximum high-cost service agents run per report. |
| COST_AI_ANALYSER_POLL_INTERVAL_MS | 15000 | No | ECS task status polling interval. |
| COST_AI_ANALYSER_TIMEOUT_MS | 2700000 | No | Maximum external task duration. |
| COST_AI_REPORT_POLL_INTERVAL_MS | 60000 | No | How often the background worker claims due schedules; minimum 10 seconds. |
| COST_AI_REPORT_LEASE_MS | 3600000 | No | Claim lease used to recover from an interrupted worker. |
| COST_AI_REPORT_BATCH_SIZE | 5 | No | Maximum schedules claimed by one worker tick; clamped from 1 to 25. |
GET /analysis/status reports meyi-cost-ai-analyser with an ecs-task
source. The analyser uses the shared ECS task role for Athena, S3, Bedrock, and
cross-account role assumption. New reports store task and private artifact
references in cost_ai_reports; existing database-backed PDF rows remain
downloadable for compatibility. Model or task failures mark only that report as
failed and do not change CUR, standard reports, or onboarding.
CUR/Athena variables
| Variable | Default | Required | Purpose |
| --- | --- | --- | --- |
| COST_DATA_SOURCE | cur | No | Compatibility setting. Cost data is always CUR/Athena only. |
| COST_CUR_DATABASE | Empty | CUR | Athena/Glue database containing the CUR table. |
| COST_CUR_TABLE | Empty | No | Optional manual Glue table override. When empty, the tenant discovery job supplies the verified table. |
| COST_CUR_OUTPUT_LOCATION | Empty | CUR | S3 URI where Athena writes query results. |
| COST_CUR_REGION | AWS_REGION or us-east-1 | No | Region for the Athena client. |
| COST_CUR_WORKGROUP | meyi-cost | No | Meyi-owned Athena workgroup used for every query. |
| AWS_REGION | us-east-1 | No | Used as the CUR region fallback. |
| COST_CUR_TENANT_COLUMN | tenant_id | No | CUR column used to isolate the Hive-style tenant partition. |
| COST_CUR_TENANT_PARTITION | Request tenant ID | No | Overrides the partition value. Avoid a global override in multi-tenant production unless every request is intentionally mapped to that partition. |
| COST_CUR_CONNECTION_COLUMN | connection_id | No | CUR column used to isolate the selected AWS source connection. The connection value is resolved from onboarding data and is not a global environment override. |
| COST_CUR_MAX_ROWS | 1000 | No | Resource-report row cap, clamped between 1 and 5000. |
| COST_CUR_ROLE_ARN | Empty | No | Dedicated SaaS-side role assumed only for central Athena/Glue/S3 queries. |
| COST_CUR_EXTERNAL_ID | Empty | No | External ID for the dedicated SaaS CUR role. |
| COST_CUR_ALLOW_TENANT_CATALOG | false | No | Compatibility switch allowing tenant metadata to override the central catalog. Keep false for centralized SaaS CUR. |
| COST_CUR_INGESTION_MODE | central | No | Describes the CUR ingestion contract returned by readiness status. |
| COST_CUR_STATUS_CACHE_MS | 300000 | No | Cache duration for the live tenant CUR readiness query. |
| COST_CUR_DISCOVERY_ENABLED | true | No | Enables tenant-specific background CUR discovery. |
| COST_CUR_DISCOVERY_INTERVAL_MS | 3600000 | No | Retry interval for non-ready tenant jobs; minimum 60000 ms. |
| COST_CUR_DISCOVERY_BATCH_SIZE | 25 | No | Maximum due tenant jobs selected per scheduler tick. |
Central catalog settings come from the SaaS backend environment. Only the
tenant partition and customer source metadata come from onboarding by default.
Legacy per-tenant catalog overrides are accepted only when
COST_CUR_ALLOW_TENANT_CATALOG=true.
Example: centralized CUR/Athena
COST_DATA_SOURCE=cur
COST_CUR_DATABASE=meyi_central_cur
COST_CUR_TABLE=
COST_CUR_OUTPUT_LOCATION=s3://meyi-saas-athena-results/
COST_CUR_REGION=us-east-1
COST_CUR_WORKGROUP=meyi-cost
COST_CUR_TENANT_COLUMN=tenant_id
COST_CUR_CONNECTION_COLUMN=connection_id
COST_CUR_MAX_ROWS=1000
COST_CUR_ROLE_ARN=arn:aws:iam::SAAS_ACCOUNT_ID:role/meyi-cur-query
COST_CUR_ALLOW_TENANT_CATALOG=false
COST_CUR_INGESTION_MODE=central
COST_CUR_DISCOVERY_ENABLED=true
COST_CUR_DISCOVERY_INTERVAL_MS=3600000Required AWS read-only access
The SaaS identity must be able to query Athena, read the Glue catalog and central CUR bucket, and write to the Athena results bucket. The customer onboarding role needs only the customer-side permissions required by the chosen CUR export/DataSync design. These policies must remain separate.
A payer/management-account role can expose organization-wide linked-account costs. A member-account role normally exposes only the costs AWS makes visible to that member. Separate member-account roles are not required merely to group organization cost by linked account when the payer role already has that data.
HTTP API
Routes are mounted below ${apiBaseUri}/cost:
| Method | Path | Purpose |
| --- | --- | --- |
| GET | /accounts | Selected or discovered AWS accounts. |
| GET | /data-status | Central CUR configuration, ingestion, readiness, row count, and freshness without exposing secrets. |
| GET | /overview | Totals, trends, accounts, services, regions, and active resources when CUR is available. |
| GET | /filter-options | Dynamic report filter values. |
| GET | /reports | Standard/comparison source data grouped by service, account, region, resource, or tag. |
| GET | /tags | Available cost-allocation tags. |
| GET | /analysis/status | Whether Cost AI is enabled and its non-secret runtime metadata. |
| GET | /analysis/latest | Latest stored analysis for the authenticated tenant. |
| POST | /analysis | Generate or reuse a cached tenant analysis for a validated date range. |
| GET | /budgets | Tenant budget rules. |
| POST | /budgets | Create a tenant budget rule. |
| DELETE | /budgets/:id | Delete a tenant budget rule. |
| GET | /budget-alert-dismissals | Current user's persisted dismissals. |
| POST | /budget-alert-dismissals | Dismiss one budget/month/status alert. |
Docker packaging
When embedding the local package in a Docker build, copy all published package
files, not only index.js:
index.js
cur.js
src/
package.json
README.md
AGENTS.mdEnvironment files should not be copied into the image. Pass required variables
with the deployment platform, Docker Compose env_file, or secret manager at
runtime.
Reference Meyi Connect integration
The current host adapter is:
meyi-connect/backend/src/plugins/cost/index.mjs
It supplies the shared Drizzle database, authenticated token middleware, tenant-level plugin enablement, and plugin lifecycle wiring.
Consumer validation checklist
- Run server syntax checks and
npm pack --dry-run. - Install or refresh the package in the host backend.
- Run
install()against a disposable/test database. - Verify unauthenticated and disabled-tenant requests are rejected by the host.
- Verify two tenants cannot access each other's accounts, budgets, or alert dismissals.
- Test CUR ready, not-configured, pending-data, and unavailable modes.
- Verify payer and member-account visibility matches the AWS role used.
- Build and start the consuming host backend before creating a Docker image.
Central CUR ingestion contract
The package implements the application side of the FinOps-style design: strict credential separation, tenant-partitioned Athena queries, fail-closed data access, and live readiness/freshness reporting. The AWS data-transfer plane must deliver each customer's CUR objects into the central CUR layout consumed by Glue. That is deployment infrastructure, not an HTTP request made with a customer access key.
Onboarding may persist these non-secret fields in session meta:
{
"curSourceBucket": "customer-cur-bucket",
"curSourcePrefix": "reports/meyi/",
"curSourceRegion": "us-east-1",
"curTenantPartition": "authenticated-tenant-id",
"curIngestionMode": "central"
}The transfer pipeline must copy or replicate only that source prefix into the
SaaS central location for the matching tenant partition. GET /data-status
then verifies that Athena can see rows for that partition. Until rows arrive,
cost endpoints remain unavailable and the UI directs the user to Cost sources
or CUR discovery with the applicable readiness message.
