@agentlensai/server 0.10.0 → 0.11.0
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/cloud/auth/api-key-middleware.d.ts +66 -0
- package/dist/cloud/auth/api-key-middleware.d.ts.map +1 -0
- package/dist/cloud/auth/api-key-middleware.js +147 -0
- package/dist/cloud/auth/api-key-middleware.js.map +1 -0
- package/dist/cloud/auth/api-keys.d.ts +90 -0
- package/dist/cloud/auth/api-keys.d.ts.map +1 -0
- package/dist/cloud/auth/api-keys.js +162 -0
- package/dist/cloud/auth/api-keys.js.map +1 -0
- package/dist/cloud/auth/audit-log.d.ts +66 -0
- package/dist/cloud/auth/audit-log.d.ts.map +1 -0
- package/dist/cloud/auth/audit-log.js +92 -0
- package/dist/cloud/auth/audit-log.js.map +1 -0
- package/dist/cloud/auth/auth-service.d.ts +77 -0
- package/dist/cloud/auth/auth-service.d.ts.map +1 -0
- package/dist/cloud/auth/auth-service.js +229 -0
- package/dist/cloud/auth/auth-service.js.map +1 -0
- package/dist/cloud/auth/brute-force.d.ts +36 -0
- package/dist/cloud/auth/brute-force.d.ts.map +1 -0
- package/dist/cloud/auth/brute-force.js +67 -0
- package/dist/cloud/auth/brute-force.js.map +1 -0
- package/dist/cloud/auth/index.d.ts +11 -0
- package/dist/cloud/auth/index.d.ts.map +1 -0
- package/dist/cloud/auth/index.js +11 -0
- package/dist/cloud/auth/index.js.map +1 -0
- package/dist/cloud/auth/jwt.d.ts +34 -0
- package/dist/cloud/auth/jwt.d.ts.map +1 -0
- package/dist/cloud/auth/jwt.js +68 -0
- package/dist/cloud/auth/jwt.js.map +1 -0
- package/dist/cloud/auth/oauth.d.ts +37 -0
- package/dist/cloud/auth/oauth.d.ts.map +1 -0
- package/dist/cloud/auth/oauth.js +120 -0
- package/dist/cloud/auth/oauth.js.map +1 -0
- package/dist/cloud/auth/passwords.d.ts +25 -0
- package/dist/cloud/auth/passwords.d.ts.map +1 -0
- package/dist/cloud/auth/passwords.js +50 -0
- package/dist/cloud/auth/passwords.js.map +1 -0
- package/dist/cloud/auth/rbac.d.ts +51 -0
- package/dist/cloud/auth/rbac.d.ts.map +1 -0
- package/dist/cloud/auth/rbac.js +89 -0
- package/dist/cloud/auth/rbac.js.map +1 -0
- package/dist/cloud/auth/tokens.d.ts +18 -0
- package/dist/cloud/auth/tokens.d.ts.map +1 -0
- package/dist/cloud/auth/tokens.js +29 -0
- package/dist/cloud/auth/tokens.js.map +1 -0
- package/dist/cloud/billing/billing-service.d.ts +44 -0
- package/dist/cloud/billing/billing-service.d.ts.map +1 -0
- package/dist/cloud/billing/billing-service.js +153 -0
- package/dist/cloud/billing/billing-service.js.map +1 -0
- package/dist/cloud/billing/index.d.ts +11 -0
- package/dist/cloud/billing/index.d.ts.map +1 -0
- package/dist/cloud/billing/index.js +11 -0
- package/dist/cloud/billing/index.js.map +1 -0
- package/dist/cloud/billing/invoice-service.d.ts +57 -0
- package/dist/cloud/billing/invoice-service.d.ts.map +1 -0
- package/dist/cloud/billing/invoice-service.js +123 -0
- package/dist/cloud/billing/invoice-service.js.map +1 -0
- package/dist/cloud/billing/plan-management.d.ts +46 -0
- package/dist/cloud/billing/plan-management.d.ts.map +1 -0
- package/dist/cloud/billing/plan-management.js +157 -0
- package/dist/cloud/billing/plan-management.js.map +1 -0
- package/dist/cloud/billing/quota-enforcement.d.ts +53 -0
- package/dist/cloud/billing/quota-enforcement.d.ts.map +1 -0
- package/dist/cloud/billing/quota-enforcement.js +143 -0
- package/dist/cloud/billing/quota-enforcement.js.map +1 -0
- package/dist/cloud/billing/stripe-client.d.ts +142 -0
- package/dist/cloud/billing/stripe-client.d.ts.map +1 -0
- package/dist/cloud/billing/stripe-client.js +169 -0
- package/dist/cloud/billing/stripe-client.js.map +1 -0
- package/dist/cloud/billing/trial-service.d.ts +47 -0
- package/dist/cloud/billing/trial-service.d.ts.map +1 -0
- package/dist/cloud/billing/trial-service.js +104 -0
- package/dist/cloud/billing/trial-service.js.map +1 -0
- package/dist/cloud/billing/usage-metering.d.ts +83 -0
- package/dist/cloud/billing/usage-metering.d.ts.map +1 -0
- package/dist/cloud/billing/usage-metering.js +174 -0
- package/dist/cloud/billing/usage-metering.js.map +1 -0
- package/dist/cloud/ingestion/backpressure.d.ts +107 -0
- package/dist/cloud/ingestion/backpressure.d.ts.map +1 -0
- package/dist/cloud/ingestion/backpressure.js +134 -0
- package/dist/cloud/ingestion/backpressure.js.map +1 -0
- package/dist/cloud/ingestion/batch-writer.d.ts +115 -0
- package/dist/cloud/ingestion/batch-writer.d.ts.map +1 -0
- package/dist/cloud/ingestion/batch-writer.js +319 -0
- package/dist/cloud/ingestion/batch-writer.js.map +1 -0
- package/dist/cloud/ingestion/dlq-manager.d.ts +116 -0
- package/dist/cloud/ingestion/dlq-manager.d.ts.map +1 -0
- package/dist/cloud/ingestion/dlq-manager.js +244 -0
- package/dist/cloud/ingestion/dlq-manager.js.map +1 -0
- package/dist/cloud/ingestion/event-queue.d.ts +105 -0
- package/dist/cloud/ingestion/event-queue.d.ts.map +1 -0
- package/dist/cloud/ingestion/event-queue.js +185 -0
- package/dist/cloud/ingestion/event-queue.js.map +1 -0
- package/dist/cloud/ingestion/gateway.d.ts +68 -0
- package/dist/cloud/ingestion/gateway.d.ts.map +1 -0
- package/dist/cloud/ingestion/gateway.js +198 -0
- package/dist/cloud/ingestion/gateway.js.map +1 -0
- package/dist/cloud/ingestion/index.d.ts +7 -0
- package/dist/cloud/ingestion/index.d.ts.map +1 -0
- package/dist/cloud/ingestion/index.js +7 -0
- package/dist/cloud/ingestion/index.js.map +1 -0
- package/dist/cloud/ingestion/rate-limiter.d.ts +73 -0
- package/dist/cloud/ingestion/rate-limiter.d.ts.map +1 -0
- package/dist/cloud/ingestion/rate-limiter.js +153 -0
- package/dist/cloud/ingestion/rate-limiter.js.map +1 -0
- package/dist/cloud/migrate.d.ts +45 -0
- package/dist/cloud/migrate.d.ts.map +1 -0
- package/dist/cloud/migrate.js +147 -0
- package/dist/cloud/migrate.js.map +1 -0
- package/dist/cloud/migration/export-import.d.ts +56 -0
- package/dist/cloud/migration/export-import.d.ts.map +1 -0
- package/dist/cloud/migration/export-import.js +289 -0
- package/dist/cloud/migration/export-import.js.map +1 -0
- package/dist/cloud/migration/index.d.ts +5 -0
- package/dist/cloud/migration/index.d.ts.map +1 -0
- package/dist/cloud/migration/index.js +5 -0
- package/dist/cloud/migration/index.js.map +1 -0
- package/dist/cloud/org-service.d.ts +68 -0
- package/dist/cloud/org-service.d.ts.map +1 -0
- package/dist/cloud/org-service.js +169 -0
- package/dist/cloud/org-service.js.map +1 -0
- package/dist/cloud/partition-maintenance.d.ts +29 -0
- package/dist/cloud/partition-maintenance.d.ts.map +1 -0
- package/dist/cloud/partition-maintenance.js +96 -0
- package/dist/cloud/partition-maintenance.js.map +1 -0
- package/dist/cloud/retention/index.d.ts +7 -0
- package/dist/cloud/retention/index.d.ts.map +1 -0
- package/dist/cloud/retention/index.js +7 -0
- package/dist/cloud/retention/index.js.map +1 -0
- package/dist/cloud/retention/partition-management.d.ts +61 -0
- package/dist/cloud/retention/partition-management.d.ts.map +1 -0
- package/dist/cloud/retention/partition-management.js +167 -0
- package/dist/cloud/retention/partition-management.js.map +1 -0
- package/dist/cloud/retention/retention-job.d.ts +70 -0
- package/dist/cloud/retention/retention-job.d.ts.map +1 -0
- package/dist/cloud/retention/retention-job.js +160 -0
- package/dist/cloud/retention/retention-job.js.map +1 -0
- package/dist/cloud/retention/retention-policy.d.ts +27 -0
- package/dist/cloud/retention/retention-policy.d.ts.map +1 -0
- package/dist/cloud/retention/retention-policy.js +36 -0
- package/dist/cloud/retention/retention-policy.js.map +1 -0
- package/dist/cloud/routes/api-key-routes.d.ts +38 -0
- package/dist/cloud/routes/api-key-routes.d.ts.map +1 -0
- package/dist/cloud/routes/api-key-routes.js +84 -0
- package/dist/cloud/routes/api-key-routes.js.map +1 -0
- package/dist/cloud/routes/audit-routes.d.ts +36 -0
- package/dist/cloud/routes/audit-routes.d.ts.map +1 -0
- package/dist/cloud/routes/audit-routes.js +47 -0
- package/dist/cloud/routes/audit-routes.js.map +1 -0
- package/dist/cloud/routes/billing-routes.d.ts +51 -0
- package/dist/cloud/routes/billing-routes.d.ts.map +1 -0
- package/dist/cloud/routes/billing-routes.js +114 -0
- package/dist/cloud/routes/billing-routes.js.map +1 -0
- package/dist/cloud/routes/onboarding-routes.d.ts +34 -0
- package/dist/cloud/routes/onboarding-routes.d.ts.map +1 -0
- package/dist/cloud/routes/onboarding-routes.js +58 -0
- package/dist/cloud/routes/onboarding-routes.js.map +1 -0
- package/dist/cloud/routes/org-routes.d.ts +80 -0
- package/dist/cloud/routes/org-routes.d.ts.map +1 -0
- package/dist/cloud/routes/org-routes.js +153 -0
- package/dist/cloud/routes/org-routes.js.map +1 -0
- package/dist/cloud/routes/usage-routes.d.ts +18 -0
- package/dist/cloud/routes/usage-routes.d.ts.map +1 -0
- package/dist/cloud/routes/usage-routes.js +66 -0
- package/dist/cloud/routes/usage-routes.js.map +1 -0
- package/dist/cloud/storage/adapter.d.ts +102 -0
- package/dist/cloud/storage/adapter.d.ts.map +1 -0
- package/dist/cloud/storage/adapter.js +21 -0
- package/dist/cloud/storage/adapter.js.map +1 -0
- package/dist/cloud/storage/index.d.ts +8 -0
- package/dist/cloud/storage/index.d.ts.map +1 -0
- package/dist/cloud/storage/index.js +7 -0
- package/dist/cloud/storage/index.js.map +1 -0
- package/dist/cloud/storage/postgres-adapter.d.ts +34 -0
- package/dist/cloud/storage/postgres-adapter.d.ts.map +1 -0
- package/dist/cloud/storage/postgres-adapter.js +544 -0
- package/dist/cloud/storage/postgres-adapter.js.map +1 -0
- package/dist/cloud/storage/sqlite-adapter.d.ts +29 -0
- package/dist/cloud/storage/sqlite-adapter.d.ts.map +1 -0
- package/dist/cloud/storage/sqlite-adapter.js +176 -0
- package/dist/cloud/storage/sqlite-adapter.js.map +1 -0
- package/dist/cloud/tenant-pool.d.ts +49 -0
- package/dist/cloud/tenant-pool.d.ts.map +1 -0
- package/dist/cloud/tenant-pool.js +61 -0
- package/dist/cloud/tenant-pool.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Org Service (S-7.1, S-7.2)
|
|
3
|
+
*
|
|
4
|
+
* Manages organizations, memberships, and invitations.
|
|
5
|
+
* Used by dashboard routes for org switching and team management.
|
|
6
|
+
*/
|
|
7
|
+
import { generateToken, hashToken } from './auth/tokens.js';
|
|
8
|
+
export class OrgServiceError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
statusCode;
|
|
11
|
+
constructor(code, message, statusCode = 400) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.statusCode = statusCode;
|
|
15
|
+
this.name = 'OrgServiceError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class OrgService {
|
|
19
|
+
db;
|
|
20
|
+
constructor(db) {
|
|
21
|
+
this.db = db;
|
|
22
|
+
}
|
|
23
|
+
// ═══════════════════════════════════════════
|
|
24
|
+
// Org CRUD (S-7.1)
|
|
25
|
+
// ═══════════════════════════════════════════
|
|
26
|
+
/** List orgs the user belongs to */
|
|
27
|
+
async listUserOrgs(userId) {
|
|
28
|
+
const result = await this.db.query(`SELECT o.id, o.name, o.slug, o.plan, o.created_at
|
|
29
|
+
FROM orgs o
|
|
30
|
+
JOIN org_members om ON om.org_id = o.id
|
|
31
|
+
WHERE om.user_id = $1
|
|
32
|
+
ORDER BY o.name`, [userId]);
|
|
33
|
+
return result.rows;
|
|
34
|
+
}
|
|
35
|
+
/** Create a new org and make the user the owner */
|
|
36
|
+
async createOrg(userId, name) {
|
|
37
|
+
if (!name || name.trim().length === 0) {
|
|
38
|
+
throw new OrgServiceError('invalid_name', 'Organization name is required');
|
|
39
|
+
}
|
|
40
|
+
const slug = name
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
43
|
+
.replace(/^-|-$/g, '')
|
|
44
|
+
+ '-' + Math.random().toString(36).slice(2, 8);
|
|
45
|
+
const result = await this.db.query(`INSERT INTO orgs (name, slug) VALUES ($1, $2) RETURNING id, name, slug, plan, created_at`, [name.trim(), slug]);
|
|
46
|
+
const org = result.rows[0];
|
|
47
|
+
await this.db.query(`INSERT INTO org_members (org_id, user_id, role) VALUES ($1, $2, 'owner')`, [org.id, userId]);
|
|
48
|
+
return org;
|
|
49
|
+
}
|
|
50
|
+
// ═══════════════════════════════════════════
|
|
51
|
+
// Team Management (S-7.2)
|
|
52
|
+
// ═══════════════════════════════════════════
|
|
53
|
+
/** List members of an org */
|
|
54
|
+
async listMembers(orgId) {
|
|
55
|
+
const result = await this.db.query(`SELECT om.user_id, u.email, u.display_name, u.avatar_url, om.role, om.joined_at
|
|
56
|
+
FROM org_members om
|
|
57
|
+
JOIN users u ON u.id = om.user_id
|
|
58
|
+
WHERE om.org_id = $1
|
|
59
|
+
ORDER BY om.joined_at`, [orgId]);
|
|
60
|
+
return result.rows;
|
|
61
|
+
}
|
|
62
|
+
/** Invite a user by email */
|
|
63
|
+
async inviteMember(orgId, invitedByUserId, email, role) {
|
|
64
|
+
// Check if already a member
|
|
65
|
+
const existing = await this.db.query(`SELECT 1 FROM org_members om JOIN users u ON u.id = om.user_id
|
|
66
|
+
WHERE om.org_id = $1 AND u.email = $2`, [orgId, email]);
|
|
67
|
+
if (existing.rows.length > 0) {
|
|
68
|
+
throw new OrgServiceError('already_member', 'This user is already a member of the organization');
|
|
69
|
+
}
|
|
70
|
+
// Check for existing pending invitation
|
|
71
|
+
const pendingInv = await this.db.query(`SELECT 1 FROM org_invitations
|
|
72
|
+
WHERE org_id = $1 AND email = $2 AND accepted_at IS NULL AND expires_at > now()`, [orgId, email]);
|
|
73
|
+
if (pendingInv.rows.length > 0) {
|
|
74
|
+
throw new OrgServiceError('already_invited', 'An invitation is already pending for this email');
|
|
75
|
+
}
|
|
76
|
+
const token = generateToken();
|
|
77
|
+
const tokenHash = hashToken(token);
|
|
78
|
+
const result = await this.db.query(`INSERT INTO org_invitations (org_id, email, role, invited_by, token, expires_at)
|
|
79
|
+
VALUES ($1, $2, $3, $4, $5, now() + interval '7 days')
|
|
80
|
+
RETURNING id, org_id, email, role, invited_by, expires_at, created_at`, [orgId, email, role, invitedByUserId, tokenHash]);
|
|
81
|
+
const invitation = result.rows[0];
|
|
82
|
+
// Get inviter name
|
|
83
|
+
const inviterResult = await this.db.query(`SELECT display_name FROM users WHERE id = $1`, [invitedByUserId]);
|
|
84
|
+
invitation.invited_by_name = inviterResult.rows[0]?.display_name ?? null;
|
|
85
|
+
return { invitation, token };
|
|
86
|
+
}
|
|
87
|
+
/** Accept an invitation by token */
|
|
88
|
+
async acceptInvitation(token, userId) {
|
|
89
|
+
const tokenHash = hashToken(token);
|
|
90
|
+
const result = await this.db.query(`SELECT id, org_id, email, role FROM org_invitations
|
|
91
|
+
WHERE token = $1 AND accepted_at IS NULL AND expires_at > now()`, [tokenHash]);
|
|
92
|
+
if (result.rows.length === 0) {
|
|
93
|
+
throw new OrgServiceError('invalid_invitation', 'Invalid or expired invitation', 404);
|
|
94
|
+
}
|
|
95
|
+
const inv = result.rows[0];
|
|
96
|
+
// Mark accepted
|
|
97
|
+
await this.db.query(`UPDATE org_invitations SET accepted_at = now() WHERE id = $1`, [inv.id]);
|
|
98
|
+
// Add membership
|
|
99
|
+
await this.db.query(`INSERT INTO org_members (org_id, user_id, role)
|
|
100
|
+
VALUES ($1, $2, $3)
|
|
101
|
+
ON CONFLICT (org_id, user_id) DO NOTHING`, [inv.org_id, userId, inv.role]);
|
|
102
|
+
return { orgId: inv.org_id, role: inv.role };
|
|
103
|
+
}
|
|
104
|
+
/** List pending invitations for an org */
|
|
105
|
+
async listInvitations(orgId) {
|
|
106
|
+
const result = await this.db.query(`SELECT i.id, i.org_id, i.email, i.role, i.invited_by,
|
|
107
|
+
u.display_name as invited_by_name, i.expires_at, i.created_at
|
|
108
|
+
FROM org_invitations i
|
|
109
|
+
LEFT JOIN users u ON u.id = i.invited_by
|
|
110
|
+
WHERE i.org_id = $1 AND i.accepted_at IS NULL AND i.expires_at > now()
|
|
111
|
+
ORDER BY i.created_at DESC`, [orgId]);
|
|
112
|
+
return result.rows;
|
|
113
|
+
}
|
|
114
|
+
/** Cancel a pending invitation */
|
|
115
|
+
async cancelInvitation(orgId, invitationId) {
|
|
116
|
+
const result = await this.db.query(`DELETE FROM org_invitations
|
|
117
|
+
WHERE id = $1 AND org_id = $2 AND accepted_at IS NULL
|
|
118
|
+
RETURNING id`, [invitationId, orgId]);
|
|
119
|
+
return result.rows.length > 0;
|
|
120
|
+
}
|
|
121
|
+
/** Change a member's role */
|
|
122
|
+
async changeMemberRole(orgId, targetUserId, newRole, actorRole) {
|
|
123
|
+
// Only owner can set owner role
|
|
124
|
+
if (newRole === 'owner' && actorRole !== 'owner') {
|
|
125
|
+
throw new OrgServiceError('forbidden', 'Only the owner can transfer ownership', 403);
|
|
126
|
+
}
|
|
127
|
+
// Can't change the role of the sole owner
|
|
128
|
+
if (newRole !== 'owner') {
|
|
129
|
+
const currentRole = await this.db.query(`SELECT role FROM org_members WHERE org_id = $1 AND user_id = $2`, [orgId, targetUserId]);
|
|
130
|
+
if (currentRole.rows[0]?.role === 'owner') {
|
|
131
|
+
// Check there's another owner
|
|
132
|
+
const ownerCount = await this.db.query(`SELECT COUNT(*) as cnt FROM org_members WHERE org_id = $1 AND role = 'owner'`, [orgId]);
|
|
133
|
+
if (parseInt(ownerCount.rows[0].cnt) <= 1) {
|
|
134
|
+
throw new OrgServiceError('last_owner', 'Cannot change role of the only owner. Transfer ownership first.', 400);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
await this.db.query(`UPDATE org_members SET role = $1 WHERE org_id = $2 AND user_id = $3`, [newRole, orgId, targetUserId]);
|
|
139
|
+
}
|
|
140
|
+
/** Remove a member from org */
|
|
141
|
+
async removeMember(orgId, targetUserId) {
|
|
142
|
+
// Can't remove the sole owner
|
|
143
|
+
const currentRole = await this.db.query(`SELECT role FROM org_members WHERE org_id = $1 AND user_id = $2`, [orgId, targetUserId]);
|
|
144
|
+
if (currentRole.rows[0]?.role === 'owner') {
|
|
145
|
+
const ownerCount = await this.db.query(`SELECT COUNT(*) as cnt FROM org_members WHERE org_id = $1 AND role = 'owner'`, [orgId]);
|
|
146
|
+
if (parseInt(ownerCount.rows[0].cnt) <= 1) {
|
|
147
|
+
throw new OrgServiceError('last_owner', 'Cannot remove the only owner', 400);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
await this.db.query(`DELETE FROM org_members WHERE org_id = $1 AND user_id = $2`, [orgId, targetUserId]);
|
|
151
|
+
}
|
|
152
|
+
/** Transfer ownership to another member */
|
|
153
|
+
async transferOwnership(orgId, fromUserId, toUserId) {
|
|
154
|
+
// Verify from is owner
|
|
155
|
+
const fromRole = await this.db.query(`SELECT role FROM org_members WHERE org_id = $1 AND user_id = $2`, [orgId, fromUserId]);
|
|
156
|
+
if (fromRole.rows[0]?.role !== 'owner') {
|
|
157
|
+
throw new OrgServiceError('not_owner', 'Only the owner can transfer ownership', 403);
|
|
158
|
+
}
|
|
159
|
+
// Verify target is a member
|
|
160
|
+
const toMember = await this.db.query(`SELECT role FROM org_members WHERE org_id = $1 AND user_id = $2`, [orgId, toUserId]);
|
|
161
|
+
if (toMember.rows.length === 0) {
|
|
162
|
+
throw new OrgServiceError('not_member', 'Target user is not a member of this organization', 400);
|
|
163
|
+
}
|
|
164
|
+
// Transfer: demote current owner to admin, promote target to owner
|
|
165
|
+
await this.db.query(`UPDATE org_members SET role = 'admin' WHERE org_id = $1 AND user_id = $2`, [orgId, fromUserId]);
|
|
166
|
+
await this.db.query(`UPDATE org_members SET role = 'owner' WHERE org_id = $1 AND user_id = $2`, [orgId, toUserId]);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=org-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"org-service.js","sourceRoot":"","sources":["../../src/cloud/org-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Partition Maintenance Job (S-1.4)
|
|
3
|
+
*
|
|
4
|
+
* Auto-creates partitions 3 months ahead and drops expired ones.
|
|
5
|
+
* Designed to be called by a daily cron job.
|
|
6
|
+
*/
|
|
7
|
+
import type { Pool } from './tenant-pool.js';
|
|
8
|
+
export interface PartitionAction {
|
|
9
|
+
action: 'created' | 'dropped';
|
|
10
|
+
partitionName: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Create a monthly partition for a table if it doesn't exist.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createMonthlyPartition(pool: Pool, table: string, year: number, month: number): Promise<string | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Run partition maintenance:
|
|
18
|
+
* - Create partitions 3 months ahead for all partitioned tables
|
|
19
|
+
* - Drop partitions older than maxRetentionMonths
|
|
20
|
+
*/
|
|
21
|
+
export declare function maintainPartitions(pool: Pool, maxRetentionMonths?: number): Promise<PartitionAction[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Get partition info for a table.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getPartitions(pool: Pool, table: string): Promise<{
|
|
26
|
+
name: string;
|
|
27
|
+
bounds: string;
|
|
28
|
+
}[]>;
|
|
29
|
+
//# sourceMappingURL=partition-maintenance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partition-maintenance.d.ts","sourceRoot":"","sources":["../../src/cloud/partition-maintenance.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;CACvB;AAiBD;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA8BxB;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,kBAAkB,GAAE,MAAW,GAC9B,OAAO,CAAC,eAAe,EAAE,CAAC,CAiD5B;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAa7C"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Partition Maintenance Job (S-1.4)
|
|
3
|
+
*
|
|
4
|
+
* Auto-creates partitions 3 months ahead and drops expired ones.
|
|
5
|
+
* Designed to be called by a daily cron job.
|
|
6
|
+
*/
|
|
7
|
+
const PARTITIONED_TABLES = [
|
|
8
|
+
{ table: 'events', column: 'timestamp' },
|
|
9
|
+
{ table: 'audit_log', column: 'created_at' },
|
|
10
|
+
{ table: 'usage_records', column: 'hour' },
|
|
11
|
+
];
|
|
12
|
+
const VALID_TABLE_NAMES = new Set(PARTITIONED_TABLES.map((t) => t.table));
|
|
13
|
+
const SAFE_IDENTIFIER_RE = /^[a-z_][a-z0-9_]*$/;
|
|
14
|
+
function assertSafeIdentifier(name) {
|
|
15
|
+
if (!SAFE_IDENTIFIER_RE.test(name)) {
|
|
16
|
+
throw new Error(`Unsafe SQL identifier: ${name}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a monthly partition for a table if it doesn't exist.
|
|
21
|
+
*/
|
|
22
|
+
export async function createMonthlyPartition(pool, table, year, month) {
|
|
23
|
+
if (!VALID_TABLE_NAMES.has(table)) {
|
|
24
|
+
throw new Error(`Invalid table for partitioning: ${table}`);
|
|
25
|
+
}
|
|
26
|
+
assertSafeIdentifier(table);
|
|
27
|
+
const partitionName = `${table}_${year}_${String(month).padStart(2, '0')}`;
|
|
28
|
+
assertSafeIdentifier(partitionName);
|
|
29
|
+
const startDate = `${year}-${String(month).padStart(2, '0')}-01`;
|
|
30
|
+
const nextMonth = month === 12 ? 1 : month + 1;
|
|
31
|
+
const nextYear = month === 12 ? year + 1 : year;
|
|
32
|
+
const endDate = `${nextYear}-${String(nextMonth).padStart(2, '0')}-01`;
|
|
33
|
+
// Check if partition already exists
|
|
34
|
+
const check = await pool.query(`SELECT 1 FROM pg_class WHERE relname = $1`, [partitionName]);
|
|
35
|
+
if (check.rows.length > 0) {
|
|
36
|
+
return null; // Already exists
|
|
37
|
+
}
|
|
38
|
+
await pool.query(`CREATE TABLE IF NOT EXISTS ${partitionName} PARTITION OF ${table} FOR VALUES FROM ($1) TO ($2)`, [startDate, endDate]);
|
|
39
|
+
return partitionName;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run partition maintenance:
|
|
43
|
+
* - Create partitions 3 months ahead for all partitioned tables
|
|
44
|
+
* - Drop partitions older than maxRetentionMonths
|
|
45
|
+
*/
|
|
46
|
+
export async function maintainPartitions(pool, maxRetentionMonths = 12) {
|
|
47
|
+
const actions = [];
|
|
48
|
+
const now = new Date();
|
|
49
|
+
// Create future partitions (current month + 3 ahead)
|
|
50
|
+
for (const { table } of PARTITIONED_TABLES) {
|
|
51
|
+
for (let offset = 0; offset <= 3; offset++) {
|
|
52
|
+
const d = new Date(now.getFullYear(), now.getMonth() + offset, 1);
|
|
53
|
+
const result = await createMonthlyPartition(pool, table, d.getFullYear(), d.getMonth() + 1);
|
|
54
|
+
if (result) {
|
|
55
|
+
actions.push({ action: 'created', partitionName: result });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Drop expired partitions
|
|
60
|
+
const cutoff = new Date(now.getFullYear(), now.getMonth() - maxRetentionMonths, 1);
|
|
61
|
+
for (const { table } of PARTITIONED_TABLES) {
|
|
62
|
+
const result = await pool.query(`SELECT inhrelid::regclass::text AS child_name,
|
|
63
|
+
pg_get_expr(c.relpartbound, c.oid) AS bound_expr
|
|
64
|
+
FROM pg_inherits
|
|
65
|
+
JOIN pg_class c ON c.oid = inhrelid
|
|
66
|
+
JOIN pg_class parent ON parent.oid = inhparent
|
|
67
|
+
WHERE parent.relname = $1`, [table]);
|
|
68
|
+
for (const row of result.rows) {
|
|
69
|
+
// Extract start date from bound: "FOR VALUES FROM ('2025-01-01') TO ('2025-02-01')"
|
|
70
|
+
const match = row.bound_expr.match(/'(\d{4}-\d{2}-\d{2})'/);
|
|
71
|
+
if (match) {
|
|
72
|
+
const startDate = new Date(match[1]);
|
|
73
|
+
if (startDate < cutoff) {
|
|
74
|
+
assertSafeIdentifier(row.child_name);
|
|
75
|
+
await pool.query(`DROP TABLE IF EXISTS ${row.child_name}`);
|
|
76
|
+
actions.push({ action: 'dropped', partitionName: row.child_name });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return actions;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get partition info for a table.
|
|
85
|
+
*/
|
|
86
|
+
export async function getPartitions(pool, table) {
|
|
87
|
+
const result = await pool.query(`SELECT inhrelid::regclass::text AS name,
|
|
88
|
+
pg_get_expr(c.relpartbound, c.oid) AS bounds
|
|
89
|
+
FROM pg_inherits
|
|
90
|
+
JOIN pg_class c ON c.oid = inhrelid
|
|
91
|
+
JOIN pg_class parent ON parent.oid = inhparent
|
|
92
|
+
WHERE parent.relname = $1
|
|
93
|
+
ORDER BY name`, [table]);
|
|
94
|
+
return result.rows;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=partition-maintenance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partition-maintenance.js","sourceRoot":"","sources":["../../src/cloud/partition-maintenance.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,kBAAkB,GAAG;IACzB,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE;IACxC,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE;IAC5C,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE;CAClC,CAAC;AAEX,MAAM,iBAAiB,GAAgB,IAAI,GAAG,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AACvF,MAAM,kBAAkB,GAAG,oBAAoB,CAAC;AAEhD,SAAS,oBAAoB,CAAC,IAAY;IACxC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,IAAU,EACV,KAAa,EACb,IAAY,EACZ,KAAa;IAEb,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,mCAAmC,KAAK,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAE5B,MAAM,aAAa,GAAG,GAAG,KAAK,IAAI,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAC3E,oBAAoB,CAAC,aAAa,CAAC,CAAC;IAEpC,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC;IACjE,MAAM,SAAS,GAAG,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAChD,MAAM,OAAO,GAAG,GAAG,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC;IAEvE,oCAAoC;IACpC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAC5B,2CAA2C,EAC3C,CAAC,aAAa,CAAC,CAChB,CAAC;IAEF,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,CAAC,iBAAiB;IAChC,CAAC;IAED,MAAM,IAAI,CAAC,KAAK,CACd,8BAA8B,aAAa,iBAAiB,KAAK,+BAA+B,EAChG,CAAC,SAAS,EAAE,OAAO,CAAC,CACrB,CAAC;IAEF,OAAO,aAAa,CAAC;AACvB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAU,EACV,qBAA6B,EAAE;IAE/B,MAAM,OAAO,GAAsB,EAAE,CAAC;IACtC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAEvB,qDAAqD;IACrD,KAAK,MAAM,EAAE,KAAK,EAAE,IAAI,kBAAkB,EAAE,CAAC;QAC3C,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;YAC3C,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC;YAClE,MAAM,MAAM,GAAG,MAAM,sBAAsB,CACzC,IAAI,EACJ,KAAK,EACL,CAAC,CAAC,WAAW,EAAE,EACf,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,CACjB,CAAC;YACF,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,GAAG,kBAAkB,EAAE,CAAC,CAAC,CAAC;IAEnF,KAAK,MAAM,EAAE,KAAK,EAAE,IAAI,kBAAkB,EAAE,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;;;;iCAK2B,EAC3B,CAAC,KAAK,CAAC,CACR,CAAC;QAEF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAoD,EAAE,CAAC;YAC9E,oFAAoF;YACpF,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;YAC5D,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBACrC,IAAI,SAAS,GAAG,MAAM,EAAE,CAAC;oBACvB,oBAAoB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;oBACrC,MAAM,IAAI,CAAC,KAAK,CAAC,wBAAwB,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;oBAC3D,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;gBACrE,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAU,EACV,KAAa;IAEb,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;;;;;mBAMe,EACf,CAAC,KAAK,CAAC,CACR,CAAC;IAEF,OAAO,MAAM,CAAC,IAA0C,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Retention & Partition Management (S-8.1, S-8.2)
|
|
3
|
+
*/
|
|
4
|
+
export { type RetentionPolicy, type OrgRetentionInfo, TIER_RETENTION, getEffectiveRetention, getRetentionCutoff, } from './retention-policy.js';
|
|
5
|
+
export { type RetentionJobResult, type RetentionWarning, type RetentionError, type OrgPurgeResult, type RetentionJobDeps, type RetentionLogger, runRetentionJob, purgeExpiredData, getExpiringDataSummary, } from './retention-job.js';
|
|
6
|
+
export { type PartitionHealthReport, type PartitionInfo, type PartitionIssue, type PartitionManagementResult, type PartitionManagementDeps, managePartitions, getGlobalMinRetentionMonths, checkPartitionHealth, checkAllPartitionHealth, } from './partition-management.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cloud/retention/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,cAAc,EACd,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,eAAe,EACf,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,gBAAgB,EAChB,2BAA2B,EAC3B,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Retention & Partition Management (S-8.1, S-8.2)
|
|
3
|
+
*/
|
|
4
|
+
export { TIER_RETENTION, getEffectiveRetention, getRetentionCutoff, } from './retention-policy.js';
|
|
5
|
+
export { runRetentionJob, purgeExpiredData, getExpiringDataSummary, } from './retention-job.js';
|
|
6
|
+
export { managePartitions, getGlobalMinRetentionMonths, checkPartitionHealth, checkAllPartitionHealth, } from './partition-management.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cloud/retention/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAGL,cAAc,EACd,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAOL,eAAe,EACf,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAML,gBAAgB,EAChB,2BAA2B,EAC3B,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production Partition Management (S-8.2)
|
|
3
|
+
*
|
|
4
|
+
* Extends the base partition-maintenance with:
|
|
5
|
+
* - Integration with retention policies (drop partitions based on min retention across orgs)
|
|
6
|
+
* - Partition health monitoring
|
|
7
|
+
* - Future partition pre-creation (3 months ahead)
|
|
8
|
+
*/
|
|
9
|
+
import type { Pool } from '../tenant-pool.js';
|
|
10
|
+
export interface PartitionHealthReport {
|
|
11
|
+
table: string;
|
|
12
|
+
totalPartitions: number;
|
|
13
|
+
partitions: PartitionInfo[];
|
|
14
|
+
issues: PartitionIssue[];
|
|
15
|
+
healthy: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface PartitionInfo {
|
|
18
|
+
name: string;
|
|
19
|
+
bounds: string;
|
|
20
|
+
startDate: string;
|
|
21
|
+
endDate: string;
|
|
22
|
+
estimatedRows?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface PartitionIssue {
|
|
25
|
+
type: 'missing_future' | 'gap' | 'too_many' | 'stale';
|
|
26
|
+
message: string;
|
|
27
|
+
partition?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface PartitionManagementResult {
|
|
30
|
+
created: string[];
|
|
31
|
+
dropped: string[];
|
|
32
|
+
errors: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface PartitionManagementDeps {
|
|
35
|
+
pool: Pool;
|
|
36
|
+
now?: Date;
|
|
37
|
+
futureMonths?: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Run full partition lifecycle management.
|
|
41
|
+
*
|
|
42
|
+
* 1. Pre-create future partitions (3 months ahead by default)
|
|
43
|
+
* 2. Drop fully expired partitions based on global minimum retention
|
|
44
|
+
*/
|
|
45
|
+
export declare function managePartitions(deps: PartitionManagementDeps): Promise<PartitionManagementResult>;
|
|
46
|
+
/**
|
|
47
|
+
* Get the minimum retention across all active orgs (in months).
|
|
48
|
+
* This determines when a shared partition can safely be dropped —
|
|
49
|
+
* only when ALL orgs' retention has expired for that time range.
|
|
50
|
+
*/
|
|
51
|
+
export declare function getGlobalMinRetentionMonths(pool: Pool): Promise<number>;
|
|
52
|
+
/**
|
|
53
|
+
* Check partition health for a table.
|
|
54
|
+
* Reports issues like missing future partitions, gaps, excessive partitions.
|
|
55
|
+
*/
|
|
56
|
+
export declare function checkPartitionHealth(pool: Pool, table: string, now?: Date, futureMonths?: number): Promise<PartitionHealthReport>;
|
|
57
|
+
/**
|
|
58
|
+
* Get health reports for all partitioned tables.
|
|
59
|
+
*/
|
|
60
|
+
export declare function checkAllPartitionHealth(pool: Pool, now?: Date): Promise<PartitionHealthReport[]>;
|
|
61
|
+
//# sourceMappingURL=partition-management.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partition-management.d.ts","sourceRoot":"","sources":["../../../src/cloud/retention/partition-management.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAW9C,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,gBAAgB,GAAG,KAAK,GAAG,UAAU,GAAG,OAAO,CAAC;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,IAAI,CAAC;IACX,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAWD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,yBAAyB,CAAC,CAiCpC;AAED;;;;GAIG;AACH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAkB7E;AAgCD;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,EACb,GAAG,GAAE,IAAiB,EACtB,YAAY,GAAE,MAAU,GACvB,OAAO,CAAC,qBAAqB,CAAC,CA2DhC;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,IAAI,EACV,GAAG,GAAE,IAAiB,GACrB,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAMlC"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production Partition Management (S-8.2)
|
|
3
|
+
*
|
|
4
|
+
* Extends the base partition-maintenance with:
|
|
5
|
+
* - Integration with retention policies (drop partitions based on min retention across orgs)
|
|
6
|
+
* - Partition health monitoring
|
|
7
|
+
* - Future partition pre-creation (3 months ahead)
|
|
8
|
+
*/
|
|
9
|
+
import { TIER_RETENTION } from './retention-policy.js';
|
|
10
|
+
import { createMonthlyPartition, getPartitions, } from '../partition-maintenance.js';
|
|
11
|
+
const PARTITIONED_TABLES = ['events', 'audit_log', 'usage_records'];
|
|
12
|
+
const SAFE_IDENTIFIER_RE = /^[a-z_][a-z0-9_]*$/;
|
|
13
|
+
function assertSafe(name) {
|
|
14
|
+
if (!SAFE_IDENTIFIER_RE.test(name))
|
|
15
|
+
throw new Error(`Unsafe identifier: ${name}`);
|
|
16
|
+
}
|
|
17
|
+
// ─── Core ────────────────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Run full partition lifecycle management.
|
|
20
|
+
*
|
|
21
|
+
* 1. Pre-create future partitions (3 months ahead by default)
|
|
22
|
+
* 2. Drop fully expired partitions based on global minimum retention
|
|
23
|
+
*/
|
|
24
|
+
export async function managePartitions(deps) {
|
|
25
|
+
const { pool, now = new Date(), futureMonths = 3 } = deps;
|
|
26
|
+
const result = { created: [], dropped: [], errors: [] };
|
|
27
|
+
// Step 1: Create future partitions
|
|
28
|
+
for (const table of PARTITIONED_TABLES) {
|
|
29
|
+
for (let offset = 0; offset <= futureMonths; offset++) {
|
|
30
|
+
const d = new Date(now.getFullYear(), now.getMonth() + offset, 1);
|
|
31
|
+
try {
|
|
32
|
+
const name = await createMonthlyPartition(pool, table, d.getFullYear(), d.getMonth() + 1);
|
|
33
|
+
if (name)
|
|
34
|
+
result.created.push(name);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
result.errors.push(`Failed to create partition for ${table} ${d.toISOString()}: ${err}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Step 2: Determine global minimum retention to decide which partitions can be dropped
|
|
42
|
+
const minRetentionMonths = await getGlobalMinRetentionMonths(pool);
|
|
43
|
+
// Step 3: Drop expired partitions
|
|
44
|
+
const cutoff = new Date(now.getFullYear(), now.getMonth() - minRetentionMonths, 1);
|
|
45
|
+
for (const table of PARTITIONED_TABLES) {
|
|
46
|
+
try {
|
|
47
|
+
const dropped = await dropExpiredPartitions(pool, table, cutoff);
|
|
48
|
+
result.dropped.push(...dropped);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
result.errors.push(`Failed to drop partitions for ${table}: ${err}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get the minimum retention across all active orgs (in months).
|
|
58
|
+
* This determines when a shared partition can safely be dropped —
|
|
59
|
+
* only when ALL orgs' retention has expired for that time range.
|
|
60
|
+
*/
|
|
61
|
+
export async function getGlobalMinRetentionMonths(pool) {
|
|
62
|
+
const { rows } = await pool.query(`SELECT DISTINCT plan FROM orgs WHERE deleted_at IS NULL`);
|
|
63
|
+
if (rows.length === 0)
|
|
64
|
+
return 12; // default safe value
|
|
65
|
+
let maxRetentionDays = 0;
|
|
66
|
+
for (const row of rows) {
|
|
67
|
+
const tier = row.plan;
|
|
68
|
+
const retention = TIER_RETENTION[tier] ?? TIER_RETENTION.free;
|
|
69
|
+
// We need the MAXIMUM retention across all orgs to know when a partition is safe to drop
|
|
70
|
+
// (a partition can only be dropped when ALL orgs' data in it has expired)
|
|
71
|
+
maxRetentionDays = Math.max(maxRetentionDays, retention.eventRetentionDays, retention.auditLogRetentionDays);
|
|
72
|
+
}
|
|
73
|
+
// Convert days to months (round up)
|
|
74
|
+
return Math.ceil(maxRetentionDays / 30);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Drop partitions whose entire time range is before the cutoff.
|
|
78
|
+
*/
|
|
79
|
+
async function dropExpiredPartitions(pool, table, cutoff) {
|
|
80
|
+
assertSafe(table);
|
|
81
|
+
const partitions = await getPartitions(pool, table);
|
|
82
|
+
const dropped = [];
|
|
83
|
+
for (const p of partitions) {
|
|
84
|
+
// Extract end date from bounds: "FOR VALUES FROM ('2025-01-01') TO ('2025-02-01')"
|
|
85
|
+
const matches = p.bounds.match(/'(\d{4}-\d{2}-\d{2})'/g);
|
|
86
|
+
if (!matches || matches.length < 2)
|
|
87
|
+
continue;
|
|
88
|
+
const endDate = new Date(matches[1].replace(/'/g, ''));
|
|
89
|
+
if (endDate <= cutoff) {
|
|
90
|
+
assertSafe(p.name);
|
|
91
|
+
await pool.query(`DROP TABLE IF EXISTS ${p.name}`);
|
|
92
|
+
dropped.push(p.name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return dropped;
|
|
96
|
+
}
|
|
97
|
+
// ─── Health Monitoring ───────────────────────────────────────────
|
|
98
|
+
/**
|
|
99
|
+
* Check partition health for a table.
|
|
100
|
+
* Reports issues like missing future partitions, gaps, excessive partitions.
|
|
101
|
+
*/
|
|
102
|
+
export async function checkPartitionHealth(pool, table, now = new Date(), futureMonths = 3) {
|
|
103
|
+
assertSafe(table);
|
|
104
|
+
const partitions = await getPartitions(pool, table);
|
|
105
|
+
const issues = [];
|
|
106
|
+
const parsedPartitions = [];
|
|
107
|
+
for (const p of partitions) {
|
|
108
|
+
const matches = p.bounds.match(/'(\d{4}-\d{2}-\d{2})'/g);
|
|
109
|
+
if (matches && matches.length >= 2) {
|
|
110
|
+
parsedPartitions.push({
|
|
111
|
+
name: p.name,
|
|
112
|
+
bounds: p.bounds,
|
|
113
|
+
startDate: matches[0].replace(/'/g, ''),
|
|
114
|
+
endDate: matches[1].replace(/'/g, ''),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Check future partitions exist
|
|
119
|
+
for (let offset = 0; offset <= futureMonths; offset++) {
|
|
120
|
+
const d = new Date(now.getFullYear(), now.getMonth() + offset, 1);
|
|
121
|
+
const expectedName = `${table}_${d.getFullYear()}_${String(d.getMonth() + 1).padStart(2, '0')}`;
|
|
122
|
+
const exists = parsedPartitions.some((p) => p.name === expectedName);
|
|
123
|
+
if (!exists) {
|
|
124
|
+
issues.push({
|
|
125
|
+
type: 'missing_future',
|
|
126
|
+
message: `Missing future partition: ${expectedName}`,
|
|
127
|
+
partition: expectedName,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Check for gaps between partitions
|
|
132
|
+
const sorted = [...parsedPartitions].sort((a, b) => a.startDate.localeCompare(b.startDate));
|
|
133
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
134
|
+
if (sorted[i].startDate !== sorted[i - 1].endDate) {
|
|
135
|
+
issues.push({
|
|
136
|
+
type: 'gap',
|
|
137
|
+
message: `Gap between ${sorted[i - 1].name} and ${sorted[i].name}`,
|
|
138
|
+
partition: sorted[i].name,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Check for excessive partitions (> 24 is unusual)
|
|
143
|
+
if (parsedPartitions.length > 24) {
|
|
144
|
+
issues.push({
|
|
145
|
+
type: 'too_many',
|
|
146
|
+
message: `Table ${table} has ${parsedPartitions.length} partitions (>24)`,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
table,
|
|
151
|
+
totalPartitions: parsedPartitions.length,
|
|
152
|
+
partitions: parsedPartitions,
|
|
153
|
+
issues,
|
|
154
|
+
healthy: issues.length === 0,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get health reports for all partitioned tables.
|
|
159
|
+
*/
|
|
160
|
+
export async function checkAllPartitionHealth(pool, now = new Date()) {
|
|
161
|
+
const reports = [];
|
|
162
|
+
for (const table of PARTITIONED_TABLES) {
|
|
163
|
+
reports.push(await checkPartitionHealth(pool, table, now));
|
|
164
|
+
}
|
|
165
|
+
return reports;
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=partition-management.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partition-management.js","sourceRoot":"","sources":["../../../src/cloud/retention/partition-management.ts"],"names":[],"mappings":"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"}
|