@urateam/core 0.1.3 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/audit/audit-e2e.test.d.ts +2 -0
- package/dist/__tests__/audit/audit-e2e.test.d.ts.map +1 -0
- package/dist/__tests__/audit/audit-e2e.test.js +56 -0
- package/dist/__tests__/audit/audit-e2e.test.js.map +1 -0
- package/dist/__tests__/audit/csv.test.d.ts +2 -0
- package/dist/__tests__/audit/csv.test.d.ts.map +1 -0
- package/dist/__tests__/audit/csv.test.js +134 -0
- package/dist/__tests__/audit/csv.test.js.map +1 -0
- package/dist/__tests__/audit/events.test.d.ts +2 -0
- package/dist/__tests__/audit/events.test.d.ts.map +1 -0
- package/dist/__tests__/audit/events.test.js +45 -0
- package/dist/__tests__/audit/events.test.js.map +1 -0
- package/dist/__tests__/audit/policy-events.test.d.ts +2 -0
- package/dist/__tests__/audit/policy-events.test.d.ts.map +1 -0
- package/dist/__tests__/audit/policy-events.test.js +49 -0
- package/dist/__tests__/audit/policy-events.test.js.map +1 -0
- package/dist/__tests__/audit/projection.test.d.ts +2 -0
- package/dist/__tests__/audit/projection.test.d.ts.map +1 -0
- package/dist/__tests__/audit/projection.test.js +76 -0
- package/dist/__tests__/audit/projection.test.js.map +1 -0
- package/dist/__tests__/audit/rbac-events.test.d.ts +2 -0
- package/dist/__tests__/audit/rbac-events.test.d.ts.map +1 -0
- package/dist/__tests__/audit/rbac-events.test.js +56 -0
- package/dist/__tests__/audit/rbac-events.test.js.map +1 -0
- package/dist/__tests__/audit/reader.test.d.ts +2 -0
- package/dist/__tests__/audit/reader.test.d.ts.map +1 -0
- package/dist/__tests__/audit/reader.test.js +133 -0
- package/dist/__tests__/audit/reader.test.js.map +1 -0
- package/dist/__tests__/audit/retention.test.d.ts +2 -0
- package/dist/__tests__/audit/retention.test.d.ts.map +1 -0
- package/dist/__tests__/audit/retention.test.js +48 -0
- package/dist/__tests__/audit/retention.test.js.map +1 -0
- package/dist/__tests__/audit/sso-events.test.d.ts +2 -0
- package/dist/__tests__/audit/sso-events.test.d.ts.map +1 -0
- package/dist/__tests__/audit/sso-events.test.js +49 -0
- package/dist/__tests__/audit/sso-events.test.js.map +1 -0
- package/dist/__tests__/audit/writer.test.d.ts +2 -0
- package/dist/__tests__/audit/writer.test.d.ts.map +1 -0
- package/dist/__tests__/audit/writer.test.js +67 -0
- package/dist/__tests__/audit/writer.test.js.map +1 -0
- package/dist/__tests__/audit-immutability.test.d.ts +2 -0
- package/dist/__tests__/audit-immutability.test.d.ts.map +1 -0
- package/dist/__tests__/audit-immutability.test.js +61 -0
- package/dist/__tests__/audit-immutability.test.js.map +1 -0
- package/dist/__tests__/audit-types.test.d.ts +2 -0
- package/dist/__tests__/audit-types.test.d.ts.map +1 -0
- package/dist/__tests__/audit-types.test.js +35 -0
- package/dist/__tests__/audit-types.test.js.map +1 -0
- package/dist/__tests__/auth/session-store.test.d.ts +2 -0
- package/dist/__tests__/auth/session-store.test.d.ts.map +1 -0
- package/dist/__tests__/auth/session-store.test.js +65 -0
- package/dist/__tests__/auth/session-store.test.js.map +1 -0
- package/dist/__tests__/auth/sso-config.test.d.ts +2 -0
- package/dist/__tests__/auth/sso-config.test.d.ts.map +1 -0
- package/dist/__tests__/auth/sso-config.test.js +75 -0
- package/dist/__tests__/auth/sso-config.test.js.map +1 -0
- package/dist/__tests__/auth/sso-feature-flag.test.d.ts +2 -0
- package/dist/__tests__/auth/sso-feature-flag.test.d.ts.map +1 -0
- package/dist/__tests__/auth/sso-feature-flag.test.js +33 -0
- package/dist/__tests__/auth/sso-feature-flag.test.js.map +1 -0
- package/dist/__tests__/auth/user-role-round-trip.test.d.ts +2 -0
- package/dist/__tests__/auth/user-role-round-trip.test.d.ts.map +1 -0
- package/dist/__tests__/auth/user-role-round-trip.test.js +30 -0
- package/dist/__tests__/auth/user-role-round-trip.test.js.map +1 -0
- package/dist/__tests__/auth/user-store.test.d.ts +2 -0
- package/dist/__tests__/auth/user-store.test.d.ts.map +1 -0
- package/dist/__tests__/auth/user-store.test.js +119 -0
- package/dist/__tests__/auth/user-store.test.js.map +1 -0
- package/dist/__tests__/auth/workos-client.test.d.ts +2 -0
- package/dist/__tests__/auth/workos-client.test.d.ts.map +1 -0
- package/dist/__tests__/auth/workos-client.test.js +17 -0
- package/dist/__tests__/auth/workos-client.test.js.map +1 -0
- package/dist/__tests__/auto-merge.test.js +2 -1
- package/dist/__tests__/auto-merge.test.js.map +1 -1
- package/dist/__tests__/cost/aggregate.test.d.ts +2 -0
- package/dist/__tests__/cost/aggregate.test.d.ts.map +1 -0
- package/dist/__tests__/cost/aggregate.test.js +358 -0
- package/dist/__tests__/cost/aggregate.test.js.map +1 -0
- package/dist/__tests__/cost/csv.test.d.ts +2 -0
- package/dist/__tests__/cost/csv.test.d.ts.map +1 -0
- package/dist/__tests__/cost/csv.test.js +109 -0
- package/dist/__tests__/cost/csv.test.js.map +1 -0
- package/dist/__tests__/cost/per-run.test.d.ts +2 -0
- package/dist/__tests__/cost/per-run.test.d.ts.map +1 -0
- package/dist/__tests__/cost/per-run.test.js +64 -0
- package/dist/__tests__/cost/per-run.test.js.map +1 -0
- package/dist/__tests__/cost/rates.test.d.ts +2 -0
- package/dist/__tests__/cost/rates.test.d.ts.map +1 -0
- package/dist/__tests__/cost/rates.test.js +47 -0
- package/dist/__tests__/cost/rates.test.js.map +1 -0
- package/dist/__tests__/cost/rollup.test.d.ts +2 -0
- package/dist/__tests__/cost/rollup.test.d.ts.map +1 -0
- package/dist/__tests__/cost/rollup.test.js +187 -0
- package/dist/__tests__/cost/rollup.test.js.map +1 -0
- package/dist/__tests__/cost-defensive.test.d.ts +2 -0
- package/dist/__tests__/cost-defensive.test.d.ts.map +1 -0
- package/dist/__tests__/cost-defensive.test.js +123 -0
- package/dist/__tests__/cost-defensive.test.js.map +1 -0
- package/dist/__tests__/cost-integration.test.d.ts +2 -0
- package/dist/__tests__/cost-integration.test.d.ts.map +1 -0
- package/dist/__tests__/cost-integration.test.js +80 -0
- package/dist/__tests__/cost-integration.test.js.map +1 -0
- package/dist/__tests__/cost-license.test.d.ts +2 -0
- package/dist/__tests__/cost-license.test.d.ts.map +1 -0
- package/dist/__tests__/cost-license.test.js +23 -0
- package/dist/__tests__/cost-license.test.js.map +1 -0
- package/dist/__tests__/cost-types.test.d.ts +2 -0
- package/dist/__tests__/cost-types.test.d.ts.map +1 -0
- package/dist/__tests__/cost-types.test.js +65 -0
- package/dist/__tests__/cost-types.test.js.map +1 -0
- package/dist/__tests__/db-audit-schema.test.d.ts +2 -0
- package/dist/__tests__/db-audit-schema.test.d.ts.map +1 -0
- package/dist/__tests__/db-audit-schema.test.js +35 -0
- package/dist/__tests__/db-audit-schema.test.js.map +1 -0
- package/dist/__tests__/db-cost-rollups-schema.test.d.ts +2 -0
- package/dist/__tests__/db-cost-rollups-schema.test.d.ts.map +1 -0
- package/dist/__tests__/db-cost-rollups-schema.test.js +28 -0
- package/dist/__tests__/db-cost-rollups-schema.test.js.map +1 -0
- package/dist/__tests__/db-rbac-schema.test.d.ts +2 -0
- package/dist/__tests__/db-rbac-schema.test.d.ts.map +1 -0
- package/dist/__tests__/db-rbac-schema.test.js +32 -0
- package/dist/__tests__/db-rbac-schema.test.js.map +1 -0
- package/dist/__tests__/db-sso-schema.test.d.ts +2 -0
- package/dist/__tests__/db-sso-schema.test.d.ts.map +1 -0
- package/dist/__tests__/db-sso-schema.test.js +48 -0
- package/dist/__tests__/db-sso-schema.test.js.map +1 -0
- package/dist/__tests__/helpers/license.d.ts +3 -0
- package/dist/__tests__/helpers/license.d.ts.map +1 -0
- package/dist/__tests__/helpers/license.js +74 -0
- package/dist/__tests__/helpers/license.js.map +1 -0
- package/dist/__tests__/license-audit-event.test.d.ts +2 -0
- package/dist/__tests__/license-audit-event.test.d.ts.map +1 -0
- package/dist/__tests__/license-audit-event.test.js +60 -0
- package/dist/__tests__/license-audit-event.test.js.map +1 -0
- package/dist/__tests__/license-end-to-end.test.d.ts +2 -0
- package/dist/__tests__/license-end-to-end.test.d.ts.map +1 -0
- package/dist/__tests__/license-end-to-end.test.js +75 -0
- package/dist/__tests__/license-end-to-end.test.js.map +1 -0
- package/dist/__tests__/license-public-key.test.d.ts +2 -0
- package/dist/__tests__/license-public-key.test.d.ts.map +1 -0
- package/dist/__tests__/license-public-key.test.js +18 -0
- package/dist/__tests__/license-public-key.test.js.map +1 -0
- package/dist/__tests__/license.test.js +195 -32
- package/dist/__tests__/license.test.js.map +1 -1
- package/dist/__tests__/pm-action-audit-events.test.d.ts +2 -0
- package/dist/__tests__/pm-action-audit-events.test.d.ts.map +1 -0
- package/dist/__tests__/pm-action-audit-events.test.js +185 -0
- package/dist/__tests__/pm-action-audit-events.test.js.map +1 -0
- package/dist/__tests__/pm-approvals.test.js +1 -1
- package/dist/__tests__/pm-approvals.test.js.map +1 -1
- package/dist/__tests__/pm-audit-retention-step.test.d.ts +2 -0
- package/dist/__tests__/pm-audit-retention-step.test.d.ts.map +1 -0
- package/dist/__tests__/pm-audit-retention-step.test.js +126 -0
- package/dist/__tests__/pm-audit-retention-step.test.js.map +1 -0
- package/dist/__tests__/pm-budget-alerts.test.d.ts +2 -0
- package/dist/__tests__/pm-budget-alerts.test.d.ts.map +1 -0
- package/dist/__tests__/pm-budget-alerts.test.js +128 -0
- package/dist/__tests__/pm-budget-alerts.test.js.map +1 -0
- package/dist/__tests__/pm-budget-refused-event.test.d.ts +2 -0
- package/dist/__tests__/pm-budget-refused-event.test.d.ts.map +1 -0
- package/dist/__tests__/pm-budget-refused-event.test.js +141 -0
- package/dist/__tests__/pm-budget-refused-event.test.js.map +1 -0
- package/dist/__tests__/pm-budget.test.js +138 -42
- package/dist/__tests__/pm-budget.test.js.map +1 -1
- package/dist/__tests__/pm-cost-rollup-step.test.d.ts +2 -0
- package/dist/__tests__/pm-cost-rollup-step.test.d.ts.map +1 -0
- package/dist/__tests__/pm-cost-rollup-step.test.js +113 -0
- package/dist/__tests__/pm-cost-rollup-step.test.js.map +1 -0
- package/dist/__tests__/pm-scheduler.test.js +48 -21
- package/dist/__tests__/pm-scheduler.test.js.map +1 -1
- package/dist/__tests__/pm-slack.test.js +37 -0
- package/dist/__tests__/pm-slack.test.js.map +1 -1
- package/dist/__tests__/pm-sso-prune-step.test.d.ts +2 -0
- package/dist/__tests__/pm-sso-prune-step.test.d.ts.map +1 -0
- package/dist/__tests__/pm-sso-prune-step.test.js +103 -0
- package/dist/__tests__/pm-sso-prune-step.test.js.map +1 -0
- package/dist/__tests__/pm-types.test.js +130 -1
- package/dist/__tests__/pm-types.test.js.map +1 -1
- package/dist/__tests__/policy/auto-merge-reviewer-gate.test.d.ts +2 -0
- package/dist/__tests__/policy/auto-merge-reviewer-gate.test.d.ts.map +1 -0
- package/dist/__tests__/policy/auto-merge-reviewer-gate.test.js +50 -0
- package/dist/__tests__/policy/auto-merge-reviewer-gate.test.js.map +1 -0
- package/dist/__tests__/policy/cost-gate.test.d.ts +2 -0
- package/dist/__tests__/policy/cost-gate.test.d.ts.map +1 -0
- package/dist/__tests__/policy/cost-gate.test.js +27 -0
- package/dist/__tests__/policy/cost-gate.test.js.map +1 -0
- package/dist/__tests__/policy/evaluate.test.d.ts +2 -0
- package/dist/__tests__/policy/evaluate.test.d.ts.map +1 -0
- package/dist/__tests__/policy/evaluate.test.js +105 -0
- package/dist/__tests__/policy/evaluate.test.js.map +1 -0
- package/dist/__tests__/policy/override.test.d.ts +2 -0
- package/dist/__tests__/policy/override.test.d.ts.map +1 -0
- package/dist/__tests__/policy/override.test.js +26 -0
- package/dist/__tests__/policy/override.test.js.map +1 -0
- package/dist/__tests__/policy/path-gate.test.d.ts +2 -0
- package/dist/__tests__/policy/path-gate.test.d.ts.map +1 -0
- package/dist/__tests__/policy/path-gate.test.js +31 -0
- package/dist/__tests__/policy/path-gate.test.js.map +1 -0
- package/dist/__tests__/policy/pr-reviewer-wiring.test.d.ts +2 -0
- package/dist/__tests__/policy/pr-reviewer-wiring.test.d.ts.map +1 -0
- package/dist/__tests__/policy/pr-reviewer-wiring.test.js +24 -0
- package/dist/__tests__/policy/pr-reviewer-wiring.test.js.map +1 -0
- package/dist/__tests__/policy/reviewer-gate.test.d.ts +2 -0
- package/dist/__tests__/policy/reviewer-gate.test.d.ts.map +1 -0
- package/dist/__tests__/policy/reviewer-gate.test.js +125 -0
- package/dist/__tests__/policy/reviewer-gate.test.js.map +1 -0
- package/dist/__tests__/policy-license.test.d.ts +2 -0
- package/dist/__tests__/policy-license.test.d.ts.map +1 -0
- package/dist/__tests__/policy-license.test.js +31 -0
- package/dist/__tests__/policy-license.test.js.map +1 -0
- package/dist/__tests__/policy-types.test.d.ts +2 -0
- package/dist/__tests__/policy-types.test.d.ts.map +1 -0
- package/dist/__tests__/policy-types.test.js +49 -0
- package/dist/__tests__/policy-types.test.js.map +1 -0
- package/dist/__tests__/ralph-gate.test.js +6 -4
- package/dist/__tests__/ralph-gate.test.js.map +1 -1
- package/dist/__tests__/rbac/bootstrap.test.d.ts +2 -0
- package/dist/__tests__/rbac/bootstrap.test.d.ts.map +1 -0
- package/dist/__tests__/rbac/bootstrap.test.js +62 -0
- package/dist/__tests__/rbac/bootstrap.test.js.map +1 -0
- package/dist/__tests__/rbac/matrix.test.d.ts +2 -0
- package/dist/__tests__/rbac/matrix.test.d.ts.map +1 -0
- package/dist/__tests__/rbac/matrix.test.js +62 -0
- package/dist/__tests__/rbac/matrix.test.js.map +1 -0
- package/dist/__tests__/rbac/user-role-store.test.d.ts +2 -0
- package/dist/__tests__/rbac/user-role-store.test.d.ts.map +1 -0
- package/dist/__tests__/rbac/user-role-store.test.js +78 -0
- package/dist/__tests__/rbac/user-role-store.test.js.map +1 -0
- package/dist/__tests__/rbac-defensive.test.d.ts +2 -0
- package/dist/__tests__/rbac-defensive.test.d.ts.map +1 -0
- package/dist/__tests__/rbac-defensive.test.js +29 -0
- package/dist/__tests__/rbac-defensive.test.js.map +1 -0
- package/dist/__tests__/rbac-integration.test.d.ts +2 -0
- package/dist/__tests__/rbac-integration.test.d.ts.map +1 -0
- package/dist/__tests__/rbac-integration.test.js +58 -0
- package/dist/__tests__/rbac-integration.test.js.map +1 -0
- package/dist/__tests__/rbac-license.test.d.ts +2 -0
- package/dist/__tests__/rbac-license.test.d.ts.map +1 -0
- package/dist/__tests__/rbac-license.test.js +27 -0
- package/dist/__tests__/rbac-license.test.js.map +1 -0
- package/dist/__tests__/reproduce-bec113-pagination-warning.test.js +14 -2
- package/dist/__tests__/reproduce-bec113-pagination-warning.test.js.map +1 -1
- package/dist/__tests__/reproduce-bec62.test.js +10 -3
- package/dist/__tests__/reproduce-bec62.test.js.map +1 -1
- package/dist/__tests__/reproduce-bec91-stuck-in-progress.test.js +24 -4
- package/dist/__tests__/reproduce-bec91-stuck-in-progress.test.js.map +1 -1
- package/dist/__tests__/server.test.js +46 -2
- package/dist/__tests__/server.test.js.map +1 -1
- package/dist/__tests__/sso-license.test.d.ts +2 -0
- package/dist/__tests__/sso-license.test.d.ts.map +1 -0
- package/dist/__tests__/sso-license.test.js +49 -0
- package/dist/__tests__/sso-license.test.js.map +1 -0
- package/dist/__tests__/start-todo.test.js +5 -0
- package/dist/__tests__/start-todo.test.js.map +1 -1
- package/dist/__tests__/webhook-handler.test.js +138 -0
- package/dist/__tests__/webhook-handler.test.js.map +1 -1
- package/dist/audit/config-fingerprint.d.ts +10 -0
- package/dist/audit/config-fingerprint.d.ts.map +1 -0
- package/dist/audit/config-fingerprint.js +20 -0
- package/dist/audit/config-fingerprint.js.map +1 -0
- package/dist/audit/csv.d.ts +8 -0
- package/dist/audit/csv.d.ts.map +1 -0
- package/dist/audit/csv.js +56 -0
- package/dist/audit/csv.js.map +1 -0
- package/dist/audit/events.d.ts +107 -0
- package/dist/audit/events.d.ts.map +1 -0
- package/dist/audit/events.js +221 -0
- package/dist/audit/events.js.map +1 -0
- package/dist/audit/index.d.ts +7 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +7 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/projection.d.ts +38 -0
- package/dist/audit/projection.d.ts.map +1 -0
- package/dist/audit/projection.js +109 -0
- package/dist/audit/projection.js.map +1 -0
- package/dist/audit/reader.d.ts +42 -0
- package/dist/audit/reader.d.ts.map +1 -0
- package/dist/audit/reader.js +243 -0
- package/dist/audit/reader.js.map +1 -0
- package/dist/audit/retention.d.ts +12 -0
- package/dist/audit/retention.d.ts.map +1 -0
- package/dist/audit/retention.js +23 -0
- package/dist/audit/retention.js.map +1 -0
- package/dist/audit/writer.d.ts +26 -0
- package/dist/audit/writer.d.ts.map +1 -0
- package/dist/audit/writer.js +54 -0
- package/dist/audit/writer.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/session-store.d.ts +17 -0
- package/dist/auth/session-store.d.ts.map +1 -0
- package/dist/auth/session-store.js +59 -0
- package/dist/auth/session-store.js.map +1 -0
- package/dist/auth/sso-config.d.ts +14 -0
- package/dist/auth/sso-config.d.ts.map +1 -0
- package/dist/auth/sso-config.js +50 -0
- package/dist/auth/sso-config.js.map +1 -0
- package/dist/auth/user-store.d.ts +33 -0
- package/dist/auth/user-store.d.ts.map +1 -0
- package/dist/auth/user-store.js +71 -0
- package/dist/auth/user-store.js.map +1 -0
- package/dist/auth/workos-client.d.ts +46 -0
- package/dist/auth/workos-client.d.ts.map +1 -0
- package/dist/auth/workos-client.js +66 -0
- package/dist/auth/workos-client.js.map +1 -0
- package/dist/cost/aggregate.d.ts +49 -0
- package/dist/cost/aggregate.d.ts.map +1 -0
- package/dist/cost/aggregate.js +364 -0
- package/dist/cost/aggregate.js.map +1 -0
- package/dist/cost/csv.d.ts +12 -0
- package/dist/cost/csv.d.ts.map +1 -0
- package/dist/cost/csv.js +79 -0
- package/dist/cost/csv.js.map +1 -0
- package/dist/cost/index.d.ts +7 -0
- package/dist/cost/index.d.ts.map +1 -0
- package/dist/cost/index.js +7 -0
- package/dist/cost/index.js.map +1 -0
- package/dist/cost/per-run.d.ts +31 -0
- package/dist/cost/per-run.d.ts.map +1 -0
- package/dist/cost/per-run.js +35 -0
- package/dist/cost/per-run.js.map +1 -0
- package/dist/cost/rates.d.ts +27 -0
- package/dist/cost/rates.d.ts.map +1 -0
- package/dist/cost/rates.js +26 -0
- package/dist/cost/rates.js.map +1 -0
- package/dist/cost/rollup.d.ts +17 -0
- package/dist/cost/rollup.d.ts.map +1 -0
- package/dist/cost/rollup.js +164 -0
- package/dist/cost/rollup.js.map +1 -0
- package/dist/cost/types.d.ts +54 -0
- package/dist/cost/types.d.ts.map +1 -0
- package/dist/cost/types.js +2 -0
- package/dist/cost/types.js.map +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +79 -1
- package/dist/db/client.js.map +1 -1
- package/dist/db/migrations/postgres/006_spend_caps.sql +14 -0
- package/dist/db/migrations/postgres/007_audit_events.sql +19 -0
- package/dist/db/migrations/postgres/008_cost_rollups.sql +19 -0
- package/dist/db/migrations/postgres/008_sso.sql +20 -0
- package/dist/db/migrations/sqlite/005_spend_caps.sql +16 -0
- package/dist/db/migrations/sqlite/006_audit_events.sql +19 -0
- package/dist/db/migrations/sqlite/007_cost_rollups.sql +19 -0
- package/dist/db/migrations/sqlite/007_sso.sql +20 -0
- package/dist/db/schema.d.ts +802 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +80 -1
- package/dist/db/schema.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/license-public-key.d.ts +15 -0
- package/dist/license-public-key.d.ts.map +1 -0
- package/dist/license-public-key.js +15 -0
- package/dist/license-public-key.js.map +1 -0
- package/dist/license.d.ts +30 -5
- package/dist/license.d.ts.map +1 -1
- package/dist/license.js +142 -12
- package/dist/license.js.map +1 -1
- package/dist/pipeline/runner.d.ts +1 -7
- package/dist/pipeline/runner.d.ts.map +1 -1
- package/dist/pipeline/runner.js +168 -21
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/pm/actions/promote.d.ts +3 -0
- package/dist/pm/actions/promote.d.ts.map +1 -1
- package/dist/pm/actions/promote.js +12 -0
- package/dist/pm/actions/promote.js.map +1 -1
- package/dist/pm/actions/resolve-approvals.d.ts.map +1 -1
- package/dist/pm/actions/resolve-approvals.js +16 -0
- package/dist/pm/actions/resolve-approvals.js.map +1 -1
- package/dist/pm/actions/start-todo.d.ts +3 -0
- package/dist/pm/actions/start-todo.d.ts.map +1 -1
- package/dist/pm/actions/start-todo.js +5 -1
- package/dist/pm/actions/start-todo.js.map +1 -1
- package/dist/pm/actions/triage.d.ts +3 -0
- package/dist/pm/actions/triage.d.ts.map +1 -1
- package/dist/pm/actions/triage.js +8 -0
- package/dist/pm/actions/triage.js.map +1 -1
- package/dist/pm/budget-alerts.d.ts +18 -0
- package/dist/pm/budget-alerts.d.ts.map +1 -0
- package/dist/pm/budget-alerts.js +100 -0
- package/dist/pm/budget-alerts.js.map +1 -0
- package/dist/pm/budget.d.ts +28 -5
- package/dist/pm/budget.d.ts.map +1 -1
- package/dist/pm/budget.js +115 -39
- package/dist/pm/budget.js.map +1 -1
- package/dist/pm/scheduler.d.ts +8 -4
- package/dist/pm/scheduler.d.ts.map +1 -1
- package/dist/pm/scheduler.js +128 -5
- package/dist/pm/scheduler.js.map +1 -1
- package/dist/pm/slack.d.ts.map +1 -1
- package/dist/pm/slack.js +11 -0
- package/dist/pm/slack.js.map +1 -1
- package/dist/pm/types.d.ts +36 -0
- package/dist/pm/types.d.ts.map +1 -1
- package/dist/pm/types.js +12 -0
- package/dist/pm/types.js.map +1 -1
- package/dist/policy/cost-gate.d.ts +3 -0
- package/dist/policy/cost-gate.d.ts.map +1 -0
- package/dist/policy/cost-gate.js +11 -0
- package/dist/policy/cost-gate.js.map +1 -0
- package/dist/policy/evaluate.d.ts +32 -0
- package/dist/policy/evaluate.d.ts.map +1 -0
- package/dist/policy/evaluate.js +61 -0
- package/dist/policy/evaluate.js.map +1 -0
- package/dist/policy/index.d.ts +7 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +7 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/policy/override.d.ts +23 -0
- package/dist/policy/override.d.ts.map +1 -0
- package/dist/policy/override.js +26 -0
- package/dist/policy/override.js.map +1 -0
- package/dist/policy/path-gate.d.ts +3 -0
- package/dist/policy/path-gate.d.ts.map +1 -0
- package/dist/policy/path-gate.js +20 -0
- package/dist/policy/path-gate.js.map +1 -0
- package/dist/policy/reviewer-gate.d.ts +45 -0
- package/dist/policy/reviewer-gate.d.ts.map +1 -0
- package/dist/policy/reviewer-gate.js +77 -0
- package/dist/policy/reviewer-gate.js.map +1 -0
- package/dist/policy/types.d.ts +7 -0
- package/dist/policy/types.d.ts.map +1 -0
- package/dist/policy/types.js +2 -0
- package/dist/policy/types.js.map +1 -0
- package/dist/rbac/errors.d.ts +7 -0
- package/dist/rbac/errors.d.ts.map +1 -0
- package/dist/rbac/errors.js +13 -0
- package/dist/rbac/errors.js.map +1 -0
- package/dist/rbac/index.d.ts +5 -0
- package/dist/rbac/index.d.ts.map +1 -0
- package/dist/rbac/index.js +5 -0
- package/dist/rbac/index.js.map +1 -0
- package/dist/rbac/matrix.d.ts +35 -0
- package/dist/rbac/matrix.d.ts.map +1 -0
- package/dist/rbac/matrix.js +42 -0
- package/dist/rbac/matrix.js.map +1 -0
- package/dist/rbac/types.d.ts +2 -0
- package/dist/rbac/types.d.ts.map +1 -0
- package/dist/rbac/types.js +2 -0
- package/dist/rbac/types.js.map +1 -0
- package/dist/rbac/user-role-store.d.ts +27 -0
- package/dist/rbac/user-role-store.d.ts.map +1 -0
- package/dist/rbac/user-role-store.js +148 -0
- package/dist/rbac/user-role-store.js.map +1 -0
- package/dist/repo/git.d.ts +9 -0
- package/dist/repo/git.d.ts.map +1 -1
- package/dist/repo/git.js +11 -0
- package/dist/repo/git.js.map +1 -1
- package/dist/repo/github.d.ts +4 -0
- package/dist/repo/github.d.ts.map +1 -1
- package/dist/repo/github.js +27 -0
- package/dist/repo/github.js.map +1 -1
- package/dist/repo/gitlab.d.ts +4 -0
- package/dist/repo/gitlab.d.ts.map +1 -1
- package/dist/repo/gitlab.js +7 -0
- package/dist/repo/gitlab.js.map +1 -1
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +16 -10
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +155 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +83 -0
- package/dist/types.js.map +1 -1
- package/dist/util/glob.d.ts +7 -0
- package/dist/util/glob.d.ts.map +1 -0
- package/dist/util/glob.js +20 -0
- package/dist/util/glob.js.map +1 -0
- package/dist/webhook/handler.d.ts +3 -0
- package/dist/webhook/handler.d.ts.map +1 -1
- package/dist/webhook/handler.js +30 -1
- package/dist/webhook/handler.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AnyDb } from "../db/client.js";
|
|
2
|
+
import type { Role } from "../rbac/types.js";
|
|
3
|
+
export interface DashboardUser {
|
|
4
|
+
id: string;
|
|
5
|
+
email: string;
|
|
6
|
+
name: string | null;
|
|
7
|
+
workosUserId: string | null;
|
|
8
|
+
createdAt: Date;
|
|
9
|
+
lastLoginAt: Date | null;
|
|
10
|
+
role: Role;
|
|
11
|
+
}
|
|
12
|
+
export interface UpsertUserInput {
|
|
13
|
+
email: string;
|
|
14
|
+
name: string | null;
|
|
15
|
+
workosUserId: string | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Insert or update a dashboard user by normalized email. Returns the
|
|
19
|
+
* canonical user id. On update, `name`, `workosUserId`, and `lastLoginAt`
|
|
20
|
+
* are refreshed; `id` and `createdAt` are preserved.
|
|
21
|
+
*
|
|
22
|
+
* Implemented as an atomic upsert (`onConflictDoUpdate` on the unique
|
|
23
|
+
* `email` column) so that two concurrent `/auth/callback` requests for the
|
|
24
|
+
* same email cannot both see `existing.length === 0` and race on INSERT.
|
|
25
|
+
* The generated `id` is only used if no row exists; on conflict we read
|
|
26
|
+
* back the canonical id from the surviving row.
|
|
27
|
+
*/
|
|
28
|
+
export declare function upsertUser(db: AnyDb, input: UpsertUserInput): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Look up a dashboard user by id. Returns `null` when not found.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getUserById(db: AnyDb, id: string): Promise<DashboardUser | null>;
|
|
33
|
+
//# sourceMappingURL=user-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-store.d.ts","sourceRoot":"","sources":["../../src/auth/user-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAMD;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,KAAK,EACT,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,MAAM,CAAC,CAgCjB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,KAAK,EACT,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAiB/B"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { eq } from "drizzle-orm";
|
|
3
|
+
import { dashboardUsers } from "../db/schema.js";
|
|
4
|
+
function normalizeEmail(email) {
|
|
5
|
+
return email.trim().toLowerCase();
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Insert or update a dashboard user by normalized email. Returns the
|
|
9
|
+
* canonical user id. On update, `name`, `workosUserId`, and `lastLoginAt`
|
|
10
|
+
* are refreshed; `id` and `createdAt` are preserved.
|
|
11
|
+
*
|
|
12
|
+
* Implemented as an atomic upsert (`onConflictDoUpdate` on the unique
|
|
13
|
+
* `email` column) so that two concurrent `/auth/callback` requests for the
|
|
14
|
+
* same email cannot both see `existing.length === 0` and race on INSERT.
|
|
15
|
+
* The generated `id` is only used if no row exists; on conflict we read
|
|
16
|
+
* back the canonical id from the surviving row.
|
|
17
|
+
*/
|
|
18
|
+
export async function upsertUser(db, input) {
|
|
19
|
+
const email = normalizeEmail(input.email);
|
|
20
|
+
const now = new Date();
|
|
21
|
+
const id = `usr_${randomUUID()}`;
|
|
22
|
+
await db
|
|
23
|
+
.insert(dashboardUsers)
|
|
24
|
+
.values({
|
|
25
|
+
id,
|
|
26
|
+
email,
|
|
27
|
+
name: input.name,
|
|
28
|
+
workosUserId: input.workosUserId,
|
|
29
|
+
createdAt: now,
|
|
30
|
+
lastLoginAt: now,
|
|
31
|
+
})
|
|
32
|
+
.onConflictDoUpdate({
|
|
33
|
+
target: dashboardUsers.email,
|
|
34
|
+
set: {
|
|
35
|
+
name: input.name,
|
|
36
|
+
workosUserId: input.workosUserId,
|
|
37
|
+
lastLoginAt: now,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
// Read back the canonical id — it may be a pre-existing row's id, not
|
|
41
|
+
// the one we just generated above.
|
|
42
|
+
const rows = await db
|
|
43
|
+
.select()
|
|
44
|
+
.from(dashboardUsers)
|
|
45
|
+
.where(eq(dashboardUsers.email, email))
|
|
46
|
+
.limit(1);
|
|
47
|
+
return rows[0].id;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Look up a dashboard user by id. Returns `null` when not found.
|
|
51
|
+
*/
|
|
52
|
+
export async function getUserById(db, id) {
|
|
53
|
+
const rows = await db
|
|
54
|
+
.select()
|
|
55
|
+
.from(dashboardUsers)
|
|
56
|
+
.where(eq(dashboardUsers.id, id))
|
|
57
|
+
.limit(1);
|
|
58
|
+
if (rows.length === 0)
|
|
59
|
+
return null;
|
|
60
|
+
const row = rows[0];
|
|
61
|
+
return {
|
|
62
|
+
id: row.id,
|
|
63
|
+
email: row.email,
|
|
64
|
+
name: row.name ?? null,
|
|
65
|
+
workosUserId: row.workosUserId ?? null,
|
|
66
|
+
createdAt: row.createdAt,
|
|
67
|
+
lastLoginAt: row.lastLoginAt ?? null,
|
|
68
|
+
role: (row.role ?? "viewer"),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=user-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-store.js","sourceRoot":"","sources":["../../src/auth/user-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEjC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAmBjD,SAAS,cAAc,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACpC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,EAAS,EACT,KAAsB;IAEtB,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,EAAE,GAAG,OAAO,UAAU,EAAE,EAAE,CAAC;IAEjC,MAAM,EAAE;SACL,MAAM,CAAC,cAAc,CAAC;SACtB,MAAM,CAAC;QACN,EAAE;QACF,KAAK;QACL,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,SAAS,EAAE,GAAG;QACd,WAAW,EAAE,GAAG;KACjB,CAAC;SACD,kBAAkB,CAAC;QAClB,MAAM,EAAE,cAAc,CAAC,KAAK;QAC5B,GAAG,EAAE;YACH,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,WAAW,EAAE,GAAG;SACjB;KACF,CAAC,CAAC;IAEL,sEAAsE;IACtE,mCAAmC;IACnC,MAAM,IAAI,GAAG,MAAM,EAAE;SAClB,MAAM,EAAE;SACR,IAAI,CAAC,cAAc,CAAC;SACpB,KAAK,CAAC,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;SACtC,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,OAAO,IAAI,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,EAAS,EACT,EAAU;IAEV,MAAM,IAAI,GAAG,MAAM,EAAE;SAClB,MAAM,EAAE;SACR,IAAI,CAAC,cAAc,CAAC;SACpB,KAAK,CAAC,EAAE,CAAC,cAAc,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;SAChC,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;IACrB,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,IAAI;QACtB,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,IAAI;QACtC,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,IAAI;QACpC,IAAI,EAAE,CAAE,GAAgC,CAAC,IAAI,IAAI,QAAQ,CAAS;KACnE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin interface over @workos-inc/node so tests can inject a stub
|
|
3
|
+
* without importing the SDK or hitting the network.
|
|
4
|
+
*/
|
|
5
|
+
export interface WorkosAuthorizeArgs {
|
|
6
|
+
clientId: string;
|
|
7
|
+
redirectUri: string;
|
|
8
|
+
state: string;
|
|
9
|
+
}
|
|
10
|
+
export interface WorkosAuthenticateArgs {
|
|
11
|
+
clientId: string;
|
|
12
|
+
code: string;
|
|
13
|
+
}
|
|
14
|
+
export interface WorkosUserProfile {
|
|
15
|
+
id: string;
|
|
16
|
+
email: string;
|
|
17
|
+
firstName: string | null;
|
|
18
|
+
lastName: string | null;
|
|
19
|
+
}
|
|
20
|
+
export interface WorkosAuthenticateResult {
|
|
21
|
+
user: WorkosUserProfile;
|
|
22
|
+
}
|
|
23
|
+
export interface WorkosClient {
|
|
24
|
+
getAuthorizationUrl(args: WorkosAuthorizeArgs): Promise<string>;
|
|
25
|
+
authenticateWithCode(args: WorkosAuthenticateArgs): Promise<WorkosAuthenticateResult>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Default WorkOS client. Lazily instantiates the SDK on first use so test
|
|
29
|
+
* environments that never call this never need the SDK installed.
|
|
30
|
+
*
|
|
31
|
+
* NOTE: The singleton cache is not keyed on apiKey. If the API key is
|
|
32
|
+
* rotated, the process must be restarted to pick up the new key. The cache
|
|
33
|
+
* is also coupled to the license cache: if a future change introduces
|
|
34
|
+
* in-process license refresh (see `_resetLicenseCache`), call
|
|
35
|
+
* `_resetWorkosClient()` too so the next call re-evaluates the gate.
|
|
36
|
+
*
|
|
37
|
+
* Throws `LicenseRequiredError` if the `sso` feature is not licensed. This is
|
|
38
|
+
* a defensive library-boundary gate: even though dashboard wiring already
|
|
39
|
+
* skips SSO mounting in OSS mode, any direct caller of this function (CLI
|
|
40
|
+
* bootstrap, future routes, third-party integrations) cannot accidentally
|
|
41
|
+
* load the WorkOS SDK without a license.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getDefaultWorkosClient(apiKey: string): Promise<WorkosClient>;
|
|
44
|
+
/** Test-only: reset the cached client. */
|
|
45
|
+
export declare function _resetWorkosClient(): void;
|
|
46
|
+
//# sourceMappingURL=workos-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workos-client.d.ts","sourceRoot":"","sources":["../../src/auth/workos-client.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAChE,oBAAoB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;CACvF;AAID;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CA0ClF;AAED,0CAA0C;AAC1C,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { checkLicense, LicenseRequiredError } from "../license.js";
|
|
2
|
+
import { createLogger } from "../logger.js";
|
|
3
|
+
const log = createLogger({ component: "auth.workos" });
|
|
4
|
+
let cached = null;
|
|
5
|
+
/**
|
|
6
|
+
* Default WorkOS client. Lazily instantiates the SDK on first use so test
|
|
7
|
+
* environments that never call this never need the SDK installed.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: The singleton cache is not keyed on apiKey. If the API key is
|
|
10
|
+
* rotated, the process must be restarted to pick up the new key. The cache
|
|
11
|
+
* is also coupled to the license cache: if a future change introduces
|
|
12
|
+
* in-process license refresh (see `_resetLicenseCache`), call
|
|
13
|
+
* `_resetWorkosClient()` too so the next call re-evaluates the gate.
|
|
14
|
+
*
|
|
15
|
+
* Throws `LicenseRequiredError` if the `sso` feature is not licensed. This is
|
|
16
|
+
* a defensive library-boundary gate: even though dashboard wiring already
|
|
17
|
+
* skips SSO mounting in OSS mode, any direct caller of this function (CLI
|
|
18
|
+
* bootstrap, future routes, third-party integrations) cannot accidentally
|
|
19
|
+
* load the WorkOS SDK without a license.
|
|
20
|
+
*/
|
|
21
|
+
export async function getDefaultWorkosClient(apiKey) {
|
|
22
|
+
const status = checkLicense();
|
|
23
|
+
if (!status.features.has("sso")) {
|
|
24
|
+
log.warn({ feature: "sso", tier: status.tier }, "getDefaultWorkosClient called without an enterprise license — refusing to load WorkOS SDK");
|
|
25
|
+
throw new LicenseRequiredError("sso", status.tier);
|
|
26
|
+
}
|
|
27
|
+
if (cached)
|
|
28
|
+
return cached;
|
|
29
|
+
// Dynamic import so the dep is loaded only when actually used.
|
|
30
|
+
// The SDK lives in @urateam/dashboard (the only package that activates SSO),
|
|
31
|
+
// so core does not declare it as a dependency. Suppress the missing-types
|
|
32
|
+
// error here — the dashboard provides the resolved module at runtime.
|
|
33
|
+
// @ts-expect-error — @workos-inc/node is an optional runtime dep provided by dashboard
|
|
34
|
+
const { WorkOS } = await import("@workos-inc/node");
|
|
35
|
+
const workos = new WorkOS(apiKey);
|
|
36
|
+
cached = {
|
|
37
|
+
async getAuthorizationUrl(args) {
|
|
38
|
+
return workos.userManagement.getAuthorizationUrl({
|
|
39
|
+
clientId: args.clientId,
|
|
40
|
+
redirectUri: args.redirectUri,
|
|
41
|
+
state: args.state,
|
|
42
|
+
provider: "authkit",
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
async authenticateWithCode(args) {
|
|
46
|
+
const result = await workos.userManagement.authenticateWithCode({
|
|
47
|
+
clientId: args.clientId,
|
|
48
|
+
code: args.code,
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
user: {
|
|
52
|
+
id: result.user.id,
|
|
53
|
+
email: result.user.email,
|
|
54
|
+
firstName: result.user.firstName ?? null,
|
|
55
|
+
lastName: result.user.lastName ?? null,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
return cached;
|
|
61
|
+
}
|
|
62
|
+
/** Test-only: reset the cached client. */
|
|
63
|
+
export function _resetWorkosClient() {
|
|
64
|
+
cached = null;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=workos-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workos-client.js","sourceRoot":"","sources":["../../src/auth/workos-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,GAAG,GAAG,YAAY,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC,CAAC;AAiCvD,IAAI,MAAM,GAAwB,IAAI,CAAC;AAEvC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,MAAc;IACzD,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAChC,GAAG,CAAC,IAAI,CACN,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,EACrC,2FAA2F,CAC5F,CAAC;QACF,MAAM,IAAI,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,6EAA6E;IAC7E,0EAA0E;IAC1E,sEAAsE;IACtE,uFAAuF;IACvF,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACpD,MAAM,MAAM,GAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,GAAG;QACP,KAAK,CAAC,mBAAmB,CAAC,IAAI;YAC5B,OAAO,MAAM,CAAC,cAAc,CAAC,mBAAmB,CAAC;gBAC/C,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,QAAQ,EAAE,SAAS;aACpB,CAAC,CAAC;QACL,CAAC;QACD,KAAK,CAAC,oBAAoB,CAAC,IAAI;YAC7B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,oBAAoB,CAAC;gBAC9D,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;aAChB,CAAC,CAAC;YACH,OAAO;gBACL,IAAI,EAAE;oBACJ,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE;oBAClB,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK;oBACxB,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI;oBACxC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI;iBACvC;aACF,CAAC;QACJ,CAAC;KACF,CAAC;IACF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AnyDb } from "../db/client.js";
|
|
2
|
+
import type { AggregateResult } from "./types.js";
|
|
3
|
+
export declare function normalizeTeamId(id: string | null | undefined): {
|
|
4
|
+
key: string;
|
|
5
|
+
label: string;
|
|
6
|
+
};
|
|
7
|
+
export interface AggregateFilters {
|
|
8
|
+
from: Date;
|
|
9
|
+
to: Date;
|
|
10
|
+
}
|
|
11
|
+
interface CostConfig {
|
|
12
|
+
costs?: {
|
|
13
|
+
hourlyEngRate?: number;
|
|
14
|
+
modelPricing?: Record<string, {
|
|
15
|
+
inputPerMillion: number;
|
|
16
|
+
outputPerMillion: number;
|
|
17
|
+
}>;
|
|
18
|
+
timeSavedPerPrDefault?: number;
|
|
19
|
+
};
|
|
20
|
+
pipelineConfigs?: Record<string, any>;
|
|
21
|
+
}
|
|
22
|
+
export declare function aggregateAll(db: AnyDb, filters: AggregateFilters, config: CostConfig, opts?: {
|
|
23
|
+
maxRuns?: number;
|
|
24
|
+
}): Promise<AggregateResult>;
|
|
25
|
+
/** Snap a Date down to the start of its UTC day (00:00:00.000 UTC). */
|
|
26
|
+
export declare function snapToUtcDayStart(d: Date): Date;
|
|
27
|
+
/**
|
|
28
|
+
* Hybrid aggregate: uses pre-computed rollup rows for whole UTC days before
|
|
29
|
+
* today, and live `aggregateAll` for today's partial data. The two halves are
|
|
30
|
+
* summed into a single `AggregateResult`.
|
|
31
|
+
*
|
|
32
|
+
* When `opts.enableRollups` is false (the default for arbitrary custom
|
|
33
|
+
* windows), falls back to pure live aggregation.
|
|
34
|
+
*
|
|
35
|
+
* IMPORTANT: `filters.from` must be at a UTC day boundary (00:00 UTC) for
|
|
36
|
+
* rollup-backed reads to be exact — the rollup table stores per-day totals.
|
|
37
|
+
* For preset windows (7d/30d/90d/365d) the caller is expected to snap `from`
|
|
38
|
+
* via `snapToUtcDayStart`. For non-aligned `from`, this function still routes
|
|
39
|
+
* through rollups but will slightly over-count by including the full first
|
|
40
|
+
* UTC day — acceptable for preset usage, unsuitable for arbitrary windows
|
|
41
|
+
* (hence the opt-in `enableRollups` flag).
|
|
42
|
+
*/
|
|
43
|
+
export declare function aggregateHybrid(db: AnyDb, filters: AggregateFilters, config: CostConfig, opts?: {
|
|
44
|
+
maxRuns?: number;
|
|
45
|
+
now?: Date;
|
|
46
|
+
enableRollups?: boolean;
|
|
47
|
+
}): Promise<AggregateResult>;
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=aggregate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../../src/cost/aggregate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAK7C,OAAO,KAAK,EAAE,eAAe,EAAuC,MAAM,YAAY,CAAC;AAmBvF,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAK7F;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,IAAI,CAAC;IACX,EAAE,EAAE,IAAI,CAAC;CACV;AAED,UAAU,UAAU;IAClB,KAAK,CAAC,EAAE;QACN,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,eAAe,EAAE,MAAM,CAAC;YAAC,gBAAgB,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACrF,qBAAqB,CAAC,EAAE,MAAM,CAAC;KAChC,CAAC;IACF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACvC;AAeD,wBAAsB,YAAY,CAChC,EAAE,EAAE,KAAK,EACT,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,UAAU,EAClB,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9B,OAAO,CAAC,eAAe,CAAC,CAiI1B;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI,CAE/C;AAyID;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CACnC,EAAE,EAAE,KAAK,EACT,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,UAAU,EAClB,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,IAAI,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACnE,OAAO,CAAC,eAAe,CAAC,CAmF1B"}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { and, gte, lt, inArray } from "drizzle-orm";
|
|
2
|
+
import { pipelineRuns, stageRuns, costRollupsDaily } from "../db/schema.js";
|
|
3
|
+
import { computeRunCost } from "./per-run.js";
|
|
4
|
+
import { createLogger } from "../logger.js";
|
|
5
|
+
import { isFeatureLicensed } from "../license.js";
|
|
6
|
+
const log = createLogger({ component: "cost.aggregate" });
|
|
7
|
+
function emptyAggregateResult(filters) {
|
|
8
|
+
return {
|
|
9
|
+
summary: {
|
|
10
|
+
window: { from: filters.from, to: filters.to },
|
|
11
|
+
runs: 0, prsMerged: 0, inputTokens: 0, outputTokens: 0,
|
|
12
|
+
dollars: 0, timeSavedHours: 0, roiMultiplier: 0,
|
|
13
|
+
},
|
|
14
|
+
byTeam: [], byRepo: [], byPipeline: [], byDay: [],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const DEFAULT_MAX_RUNS = 10_000;
|
|
18
|
+
const UNASSIGNED_TEAM = "(unassigned)";
|
|
19
|
+
export function normalizeTeamId(id) {
|
|
20
|
+
if (id === null || id === undefined || id === "") {
|
|
21
|
+
return { key: "team:unassigned", label: UNASSIGNED_TEAM };
|
|
22
|
+
}
|
|
23
|
+
return { key: `team:${id}`, label: id };
|
|
24
|
+
}
|
|
25
|
+
function emptyBucket(key, label) {
|
|
26
|
+
return {
|
|
27
|
+
key, label, runs: 0, prsMerged: 0,
|
|
28
|
+
inputTokens: 0, outputTokens: 0,
|
|
29
|
+
dollars: 0, timeSavedHours: 0, roiMultiplier: 0,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function finalizeRoi(row, hourlyRate) {
|
|
33
|
+
if (row.dollars === 0)
|
|
34
|
+
return row.timeSavedHours > 0 ? Infinity : 0;
|
|
35
|
+
return (row.timeSavedHours * hourlyRate) / row.dollars;
|
|
36
|
+
}
|
|
37
|
+
export async function aggregateAll(db, filters, config, opts = {}) {
|
|
38
|
+
if (!isFeatureLicensed("cost-roi")) {
|
|
39
|
+
log.warn({ feature: "cost-roi" }, "aggregateAll called without an enterprise license — returning empty result");
|
|
40
|
+
return emptyAggregateResult(filters);
|
|
41
|
+
}
|
|
42
|
+
const hourlyRate = config.costs?.hourlyEngRate ?? 50;
|
|
43
|
+
const maxRuns = opts.maxRuns ?? DEFAULT_MAX_RUNS;
|
|
44
|
+
const runs = await db.select().from(pipelineRuns).where(and(gte(pipelineRuns.completedAt, filters.from),
|
|
45
|
+
// half-open [from, to) — matches rollup.ts convention and avoids double-counting at the boundary
|
|
46
|
+
lt(pipelineRuns.completedAt, filters.to)));
|
|
47
|
+
let truncated = false;
|
|
48
|
+
if (runs.length > maxRuns) {
|
|
49
|
+
log.warn({ runs: runs.length, maxRuns, from: filters.from, to: filters.to }, "aggregateAll: runs exceed cap, truncating to most recent");
|
|
50
|
+
// Sort by completedAt DESC (nulls last) and keep only the most recent `maxRuns`.
|
|
51
|
+
runs.sort((a, b) => {
|
|
52
|
+
const ta = a.completedAt ? new Date(a.completedAt).getTime() : 0;
|
|
53
|
+
const tb = b.completedAt ? new Date(b.completedAt).getTime() : 0;
|
|
54
|
+
return tb - ta;
|
|
55
|
+
});
|
|
56
|
+
runs.splice(maxRuns);
|
|
57
|
+
truncated = true;
|
|
58
|
+
}
|
|
59
|
+
if (runs.length === 0) {
|
|
60
|
+
return {
|
|
61
|
+
summary: {
|
|
62
|
+
window: { from: filters.from, to: filters.to },
|
|
63
|
+
runs: 0, prsMerged: 0, inputTokens: 0, outputTokens: 0,
|
|
64
|
+
dollars: 0, timeSavedHours: 0, roiMultiplier: 0,
|
|
65
|
+
},
|
|
66
|
+
byTeam: [], byRepo: [], byPipeline: [], byDay: [],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const runIds = runs.map((r) => r.id);
|
|
70
|
+
const stages = await db.select().from(stageRuns).where(inArray(stageRuns.pipelineRunId, runIds));
|
|
71
|
+
const stagesByRun = new Map();
|
|
72
|
+
for (const s of stages) {
|
|
73
|
+
const arr = stagesByRun.get(s.pipelineRunId) ?? [];
|
|
74
|
+
arr.push(s);
|
|
75
|
+
stagesByRun.set(s.pipelineRunId, arr);
|
|
76
|
+
}
|
|
77
|
+
const summary = {
|
|
78
|
+
window: { from: filters.from, to: filters.to },
|
|
79
|
+
runs: 0, prsMerged: 0, inputTokens: 0, outputTokens: 0,
|
|
80
|
+
dollars: 0, timeSavedHours: 0, roiMultiplier: 0,
|
|
81
|
+
...(truncated ? { truncated: true } : {}),
|
|
82
|
+
};
|
|
83
|
+
const byTeam = new Map();
|
|
84
|
+
const byRepo = new Map();
|
|
85
|
+
const byPipeline = new Map();
|
|
86
|
+
const byDay = new Map();
|
|
87
|
+
for (const run of runs) {
|
|
88
|
+
const runStages = stagesByRun.get(run.id) ?? [];
|
|
89
|
+
const cost = computeRunCost(run, runStages, config);
|
|
90
|
+
const isMergedPr = run.status === "completed" && run.runType !== "review-feedback";
|
|
91
|
+
summary.runs += 1;
|
|
92
|
+
summary.inputTokens += cost.inputTokens;
|
|
93
|
+
summary.outputTokens += cost.outputTokens;
|
|
94
|
+
summary.dollars += cost.dollars;
|
|
95
|
+
summary.timeSavedHours += cost.timeSavedHours;
|
|
96
|
+
if (isMergedPr)
|
|
97
|
+
summary.prsMerged += 1;
|
|
98
|
+
// completedAt is guaranteed non-null here — the WHERE clause above filters
|
|
99
|
+
// on `gte(completedAt, from)` and `lt(completedAt, to)`, which excludes
|
|
100
|
+
// rows where completedAt IS NULL (SQL NULL comparisons are falsy).
|
|
101
|
+
const dateStr = new Date(run.completedAt).toISOString().slice(0, 10);
|
|
102
|
+
let dr = byDay.get(dateStr);
|
|
103
|
+
if (!dr) {
|
|
104
|
+
dr = { date: dateStr, runs: 0, prsMerged: 0, dollars: 0, timeSavedHours: 0 };
|
|
105
|
+
byDay.set(dateStr, dr);
|
|
106
|
+
}
|
|
107
|
+
dr.runs += 1;
|
|
108
|
+
dr.dollars += cost.dollars;
|
|
109
|
+
dr.timeSavedHours += cost.timeSavedHours;
|
|
110
|
+
if (isMergedPr)
|
|
111
|
+
dr.prsMerged += 1;
|
|
112
|
+
const { key: teamKey, label: teamLabel } = normalizeTeamId(run.linearTeamId);
|
|
113
|
+
if (!byTeam.has(teamKey))
|
|
114
|
+
byTeam.set(teamKey, emptyBucket(teamKey, teamLabel));
|
|
115
|
+
const tb = byTeam.get(teamKey);
|
|
116
|
+
tb.runs += 1;
|
|
117
|
+
tb.inputTokens += cost.inputTokens;
|
|
118
|
+
tb.outputTokens += cost.outputTokens;
|
|
119
|
+
tb.dollars += cost.dollars;
|
|
120
|
+
tb.timeSavedHours += cost.timeSavedHours;
|
|
121
|
+
if (isMergedPr)
|
|
122
|
+
tb.prsMerged += 1;
|
|
123
|
+
const repoKey = `repo:${run.repoUrl}`;
|
|
124
|
+
if (!byRepo.has(repoKey))
|
|
125
|
+
byRepo.set(repoKey, emptyBucket(repoKey, run.repoUrl));
|
|
126
|
+
const rb = byRepo.get(repoKey);
|
|
127
|
+
rb.runs += 1;
|
|
128
|
+
rb.inputTokens += cost.inputTokens;
|
|
129
|
+
rb.outputTokens += cost.outputTokens;
|
|
130
|
+
rb.dollars += cost.dollars;
|
|
131
|
+
rb.timeSavedHours += cost.timeSavedHours;
|
|
132
|
+
if (isMergedPr)
|
|
133
|
+
rb.prsMerged += 1;
|
|
134
|
+
const pipelineKey = `pipeline:${run.pipelineKey}`;
|
|
135
|
+
if (!byPipeline.has(pipelineKey))
|
|
136
|
+
byPipeline.set(pipelineKey, emptyBucket(pipelineKey, run.pipelineKey));
|
|
137
|
+
const pb = byPipeline.get(pipelineKey);
|
|
138
|
+
pb.runs += 1;
|
|
139
|
+
pb.inputTokens += cost.inputTokens;
|
|
140
|
+
pb.outputTokens += cost.outputTokens;
|
|
141
|
+
pb.dollars += cost.dollars;
|
|
142
|
+
pb.timeSavedHours += cost.timeSavedHours;
|
|
143
|
+
if (isMergedPr)
|
|
144
|
+
pb.prsMerged += 1;
|
|
145
|
+
}
|
|
146
|
+
summary.roiMultiplier = finalizeRoi(summary, hourlyRate);
|
|
147
|
+
const sort = (a, b) => b.dollars - a.dollars;
|
|
148
|
+
const finalize = (rows) => {
|
|
149
|
+
for (const r of rows)
|
|
150
|
+
r.roiMultiplier = finalizeRoi(r, hourlyRate);
|
|
151
|
+
return rows.sort(sort);
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
summary,
|
|
155
|
+
byTeam: finalize(Array.from(byTeam.values())),
|
|
156
|
+
byRepo: finalize(Array.from(byRepo.values())),
|
|
157
|
+
byPipeline: finalize(Array.from(byPipeline.values())),
|
|
158
|
+
byDay: Array.from(byDay.values()).sort((a, b) => a.date.localeCompare(b.date)),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** Snap a Date down to the start of its UTC day (00:00:00.000 UTC). */
|
|
162
|
+
export function snapToUtcDayStart(d) {
|
|
163
|
+
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
|
164
|
+
}
|
|
165
|
+
function utcDateStr(d) {
|
|
166
|
+
return d.toISOString().slice(0, 10);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Aggregate pre-computed rollup rows for UTC dates in [fromDate, toDate).
|
|
170
|
+
* Both bounds are YYYY-MM-DD strings. Returns an AggregateResult whose
|
|
171
|
+
* window.from/to reflect the caller-supplied Date objects (not the date
|
|
172
|
+
* strings), so the summary is consistent with the caller's framing.
|
|
173
|
+
*/
|
|
174
|
+
async function aggregateFromRollups(db, fromDate, toDate, window, hourlyRate) {
|
|
175
|
+
// Half-open [fromDate, toDate) — use gte(date) + lt(date) on the date string.
|
|
176
|
+
const rows = await db.select().from(costRollupsDaily).where(and(gte(costRollupsDaily.date, fromDate), lt(costRollupsDaily.date, toDate)));
|
|
177
|
+
const summary = {
|
|
178
|
+
window,
|
|
179
|
+
runs: 0, prsMerged: 0, inputTokens: 0, outputTokens: 0,
|
|
180
|
+
dollars: 0, timeSavedHours: 0, roiMultiplier: 0,
|
|
181
|
+
};
|
|
182
|
+
const byTeam = new Map();
|
|
183
|
+
const byRepo = new Map();
|
|
184
|
+
const byPipeline = new Map();
|
|
185
|
+
const byDay = new Map();
|
|
186
|
+
for (const r of rows) {
|
|
187
|
+
summary.runs += r.runs;
|
|
188
|
+
summary.prsMerged += r.prsMerged;
|
|
189
|
+
summary.inputTokens += r.inputTokens;
|
|
190
|
+
summary.outputTokens += r.outputTokens;
|
|
191
|
+
summary.dollars += r.dollars;
|
|
192
|
+
summary.timeSavedHours += r.timeSavedHours;
|
|
193
|
+
// Rollup rows are already bucketed by date × pipeline × team × repo,
|
|
194
|
+
// so multiple rollup rows can share the same date.
|
|
195
|
+
let dr = byDay.get(r.date);
|
|
196
|
+
if (!dr) {
|
|
197
|
+
dr = { date: r.date, runs: 0, prsMerged: 0, dollars: 0, timeSavedHours: 0 };
|
|
198
|
+
byDay.set(r.date, dr);
|
|
199
|
+
}
|
|
200
|
+
dr.runs += r.runs;
|
|
201
|
+
dr.prsMerged += r.prsMerged;
|
|
202
|
+
dr.dollars += r.dollars;
|
|
203
|
+
dr.timeSavedHours += r.timeSavedHours;
|
|
204
|
+
// rollup stores linearTeamId as "" sentinel for unassigned (see rollup.ts)
|
|
205
|
+
const teamId = r.linearTeamId === "" ? null : r.linearTeamId;
|
|
206
|
+
const { key: teamKey, label: teamLabel } = normalizeTeamId(teamId);
|
|
207
|
+
if (!byTeam.has(teamKey))
|
|
208
|
+
byTeam.set(teamKey, emptyBucket(teamKey, teamLabel));
|
|
209
|
+
const tb = byTeam.get(teamKey);
|
|
210
|
+
tb.runs += r.runs;
|
|
211
|
+
tb.prsMerged += r.prsMerged;
|
|
212
|
+
tb.inputTokens += r.inputTokens;
|
|
213
|
+
tb.outputTokens += r.outputTokens;
|
|
214
|
+
tb.dollars += r.dollars;
|
|
215
|
+
tb.timeSavedHours += r.timeSavedHours;
|
|
216
|
+
const repoKey = `repo:${r.repoUrl}`;
|
|
217
|
+
if (!byRepo.has(repoKey))
|
|
218
|
+
byRepo.set(repoKey, emptyBucket(repoKey, r.repoUrl));
|
|
219
|
+
const rb = byRepo.get(repoKey);
|
|
220
|
+
rb.runs += r.runs;
|
|
221
|
+
rb.prsMerged += r.prsMerged;
|
|
222
|
+
rb.inputTokens += r.inputTokens;
|
|
223
|
+
rb.outputTokens += r.outputTokens;
|
|
224
|
+
rb.dollars += r.dollars;
|
|
225
|
+
rb.timeSavedHours += r.timeSavedHours;
|
|
226
|
+
const pipelineKey = `pipeline:${r.pipelineKey}`;
|
|
227
|
+
if (!byPipeline.has(pipelineKey))
|
|
228
|
+
byPipeline.set(pipelineKey, emptyBucket(pipelineKey, r.pipelineKey));
|
|
229
|
+
const pb = byPipeline.get(pipelineKey);
|
|
230
|
+
pb.runs += r.runs;
|
|
231
|
+
pb.prsMerged += r.prsMerged;
|
|
232
|
+
pb.inputTokens += r.inputTokens;
|
|
233
|
+
pb.outputTokens += r.outputTokens;
|
|
234
|
+
pb.dollars += r.dollars;
|
|
235
|
+
pb.timeSavedHours += r.timeSavedHours;
|
|
236
|
+
}
|
|
237
|
+
summary.roiMultiplier = finalizeRoi(summary, hourlyRate);
|
|
238
|
+
const sort = (a, b) => b.dollars - a.dollars;
|
|
239
|
+
const finalize = (rows) => {
|
|
240
|
+
for (const r of rows)
|
|
241
|
+
r.roiMultiplier = finalizeRoi(r, hourlyRate);
|
|
242
|
+
return rows.sort(sort);
|
|
243
|
+
};
|
|
244
|
+
return {
|
|
245
|
+
summary,
|
|
246
|
+
byTeam: finalize(Array.from(byTeam.values())),
|
|
247
|
+
byRepo: finalize(Array.from(byRepo.values())),
|
|
248
|
+
byPipeline: finalize(Array.from(byPipeline.values())),
|
|
249
|
+
byDay: Array.from(byDay.values()).sort((a, b) => a.date.localeCompare(b.date)),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function mergeDailyRows(a, b) {
|
|
253
|
+
const byDate = new Map();
|
|
254
|
+
for (const row of [...a, ...b]) {
|
|
255
|
+
const existing = byDate.get(row.date);
|
|
256
|
+
if (!existing) {
|
|
257
|
+
byDate.set(row.date, { ...row });
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
existing.runs += row.runs;
|
|
261
|
+
existing.prsMerged += row.prsMerged;
|
|
262
|
+
existing.dollars += row.dollars;
|
|
263
|
+
existing.timeSavedHours += row.timeSavedHours;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return Array.from(byDate.values()).sort((x, y) => x.date.localeCompare(y.date));
|
|
267
|
+
}
|
|
268
|
+
function mergeBreakdowns(a, b, hourlyRate) {
|
|
269
|
+
const byKey = new Map();
|
|
270
|
+
for (const row of [...a, ...b]) {
|
|
271
|
+
const existing = byKey.get(row.key);
|
|
272
|
+
if (!existing) {
|
|
273
|
+
byKey.set(row.key, { ...row });
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
existing.runs += row.runs;
|
|
277
|
+
existing.prsMerged += row.prsMerged;
|
|
278
|
+
existing.inputTokens += row.inputTokens;
|
|
279
|
+
existing.outputTokens += row.outputTokens;
|
|
280
|
+
existing.dollars += row.dollars;
|
|
281
|
+
existing.timeSavedHours += row.timeSavedHours;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const merged = Array.from(byKey.values());
|
|
285
|
+
for (const r of merged)
|
|
286
|
+
r.roiMultiplier = finalizeRoi(r, hourlyRate);
|
|
287
|
+
return merged.sort((x, y) => y.dollars - x.dollars);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Hybrid aggregate: uses pre-computed rollup rows for whole UTC days before
|
|
291
|
+
* today, and live `aggregateAll` for today's partial data. The two halves are
|
|
292
|
+
* summed into a single `AggregateResult`.
|
|
293
|
+
*
|
|
294
|
+
* When `opts.enableRollups` is false (the default for arbitrary custom
|
|
295
|
+
* windows), falls back to pure live aggregation.
|
|
296
|
+
*
|
|
297
|
+
* IMPORTANT: `filters.from` must be at a UTC day boundary (00:00 UTC) for
|
|
298
|
+
* rollup-backed reads to be exact — the rollup table stores per-day totals.
|
|
299
|
+
* For preset windows (7d/30d/90d/365d) the caller is expected to snap `from`
|
|
300
|
+
* via `snapToUtcDayStart`. For non-aligned `from`, this function still routes
|
|
301
|
+
* through rollups but will slightly over-count by including the full first
|
|
302
|
+
* UTC day — acceptable for preset usage, unsuitable for arbitrary windows
|
|
303
|
+
* (hence the opt-in `enableRollups` flag).
|
|
304
|
+
*/
|
|
305
|
+
export async function aggregateHybrid(db, filters, config, opts = {}) {
|
|
306
|
+
if (!isFeatureLicensed("cost-roi")) {
|
|
307
|
+
log.warn({ feature: "cost-roi" }, "aggregateHybrid called without an enterprise license — returning empty result");
|
|
308
|
+
return emptyAggregateResult(filters);
|
|
309
|
+
}
|
|
310
|
+
const enableRollups = opts.enableRollups ?? false;
|
|
311
|
+
if (!enableRollups) {
|
|
312
|
+
return aggregateAll(db, filters, config, opts);
|
|
313
|
+
}
|
|
314
|
+
const hourlyRate = config.costs?.hourlyEngRate ?? 50;
|
|
315
|
+
const now = opts.now ?? new Date();
|
|
316
|
+
const todayUtcStart = snapToUtcDayStart(now);
|
|
317
|
+
// If the window doesn't span any completed UTC days, fall through to pure live.
|
|
318
|
+
if (filters.from >= todayUtcStart) {
|
|
319
|
+
return aggregateAll(db, filters, config, opts);
|
|
320
|
+
}
|
|
321
|
+
// Compute the rollup window (whole UTC days) and the live remainder (today).
|
|
322
|
+
const rollupEndDateStr = utcDateStr(todayUtcStart);
|
|
323
|
+
const rollupFromDateStr = utcDateStr(filters.from);
|
|
324
|
+
// If the user's window ends before today's UTC midnight, rollup covers the
|
|
325
|
+
// full window and there's no live component.
|
|
326
|
+
const rollupOnlyCutoff = filters.to <= todayUtcStart ? filters.to : todayUtcStart;
|
|
327
|
+
const rollupToDateStr = utcDateStr(rollupOnlyCutoff);
|
|
328
|
+
const rollupPart = await aggregateFromRollups(db, rollupFromDateStr,
|
|
329
|
+
// Upper bound is exclusive (lt on date string). `rollupEndDateStr` is
|
|
330
|
+
// today's date, so lt excludes today and includes through yesterday.
|
|
331
|
+
// When the window ends before today (rollup-only path), the upper bound
|
|
332
|
+
// is the window's `to` date so lt(date, windowTo) includes everything
|
|
333
|
+
// strictly before windowTo's UTC date.
|
|
334
|
+
rollupOnlyCutoff < todayUtcStart ? rollupToDateStr : rollupEndDateStr, { from: filters.from, to: filters.to }, hourlyRate);
|
|
335
|
+
// If the window ends before today's midnight, we're done.
|
|
336
|
+
if (filters.to <= todayUtcStart) {
|
|
337
|
+
log.debug({ from: rollupFromDateStr, to: rollupToDateStr }, "aggregateHybrid: rollup-only path");
|
|
338
|
+
return rollupPart;
|
|
339
|
+
}
|
|
340
|
+
// Live query for today's partial data [todayUtcStart, filters.to).
|
|
341
|
+
const livePart = await aggregateAll(db, { from: todayUtcStart, to: filters.to }, config, opts);
|
|
342
|
+
log.debug({ rollupDays: rollupFromDateStr + "..." + rollupEndDateStr, liveFrom: todayUtcStart.toISOString() }, "aggregateHybrid: rollup + live merge");
|
|
343
|
+
// Merge summary
|
|
344
|
+
const mergedSummary = {
|
|
345
|
+
window: { from: filters.from, to: filters.to },
|
|
346
|
+
runs: rollupPart.summary.runs + livePart.summary.runs,
|
|
347
|
+
prsMerged: rollupPart.summary.prsMerged + livePart.summary.prsMerged,
|
|
348
|
+
inputTokens: rollupPart.summary.inputTokens + livePart.summary.inputTokens,
|
|
349
|
+
outputTokens: rollupPart.summary.outputTokens + livePart.summary.outputTokens,
|
|
350
|
+
dollars: rollupPart.summary.dollars + livePart.summary.dollars,
|
|
351
|
+
timeSavedHours: rollupPart.summary.timeSavedHours + livePart.summary.timeSavedHours,
|
|
352
|
+
roiMultiplier: 0,
|
|
353
|
+
...(livePart.summary.truncated ? { truncated: true } : {}),
|
|
354
|
+
};
|
|
355
|
+
mergedSummary.roiMultiplier = finalizeRoi(mergedSummary, hourlyRate);
|
|
356
|
+
return {
|
|
357
|
+
summary: mergedSummary,
|
|
358
|
+
byTeam: mergeBreakdowns(rollupPart.byTeam, livePart.byTeam, hourlyRate),
|
|
359
|
+
byRepo: mergeBreakdowns(rollupPart.byRepo, livePart.byRepo, hourlyRate),
|
|
360
|
+
byPipeline: mergeBreakdowns(rollupPart.byPipeline, livePart.byPipeline, hourlyRate),
|
|
361
|
+
byDay: mergeDailyRows(rollupPart.byDay, livePart.byDay),
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
//# sourceMappingURL=aggregate.js.map
|