@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.
Files changed (185) hide show
  1. package/dist/cloud/auth/api-key-middleware.d.ts +66 -0
  2. package/dist/cloud/auth/api-key-middleware.d.ts.map +1 -0
  3. package/dist/cloud/auth/api-key-middleware.js +147 -0
  4. package/dist/cloud/auth/api-key-middleware.js.map +1 -0
  5. package/dist/cloud/auth/api-keys.d.ts +90 -0
  6. package/dist/cloud/auth/api-keys.d.ts.map +1 -0
  7. package/dist/cloud/auth/api-keys.js +162 -0
  8. package/dist/cloud/auth/api-keys.js.map +1 -0
  9. package/dist/cloud/auth/audit-log.d.ts +66 -0
  10. package/dist/cloud/auth/audit-log.d.ts.map +1 -0
  11. package/dist/cloud/auth/audit-log.js +92 -0
  12. package/dist/cloud/auth/audit-log.js.map +1 -0
  13. package/dist/cloud/auth/auth-service.d.ts +77 -0
  14. package/dist/cloud/auth/auth-service.d.ts.map +1 -0
  15. package/dist/cloud/auth/auth-service.js +229 -0
  16. package/dist/cloud/auth/auth-service.js.map +1 -0
  17. package/dist/cloud/auth/brute-force.d.ts +36 -0
  18. package/dist/cloud/auth/brute-force.d.ts.map +1 -0
  19. package/dist/cloud/auth/brute-force.js +67 -0
  20. package/dist/cloud/auth/brute-force.js.map +1 -0
  21. package/dist/cloud/auth/index.d.ts +11 -0
  22. package/dist/cloud/auth/index.d.ts.map +1 -0
  23. package/dist/cloud/auth/index.js +11 -0
  24. package/dist/cloud/auth/index.js.map +1 -0
  25. package/dist/cloud/auth/jwt.d.ts +34 -0
  26. package/dist/cloud/auth/jwt.d.ts.map +1 -0
  27. package/dist/cloud/auth/jwt.js +68 -0
  28. package/dist/cloud/auth/jwt.js.map +1 -0
  29. package/dist/cloud/auth/oauth.d.ts +37 -0
  30. package/dist/cloud/auth/oauth.d.ts.map +1 -0
  31. package/dist/cloud/auth/oauth.js +120 -0
  32. package/dist/cloud/auth/oauth.js.map +1 -0
  33. package/dist/cloud/auth/passwords.d.ts +25 -0
  34. package/dist/cloud/auth/passwords.d.ts.map +1 -0
  35. package/dist/cloud/auth/passwords.js +50 -0
  36. package/dist/cloud/auth/passwords.js.map +1 -0
  37. package/dist/cloud/auth/rbac.d.ts +51 -0
  38. package/dist/cloud/auth/rbac.d.ts.map +1 -0
  39. package/dist/cloud/auth/rbac.js +89 -0
  40. package/dist/cloud/auth/rbac.js.map +1 -0
  41. package/dist/cloud/auth/tokens.d.ts +18 -0
  42. package/dist/cloud/auth/tokens.d.ts.map +1 -0
  43. package/dist/cloud/auth/tokens.js +29 -0
  44. package/dist/cloud/auth/tokens.js.map +1 -0
  45. package/dist/cloud/billing/billing-service.d.ts +44 -0
  46. package/dist/cloud/billing/billing-service.d.ts.map +1 -0
  47. package/dist/cloud/billing/billing-service.js +153 -0
  48. package/dist/cloud/billing/billing-service.js.map +1 -0
  49. package/dist/cloud/billing/index.d.ts +11 -0
  50. package/dist/cloud/billing/index.d.ts.map +1 -0
  51. package/dist/cloud/billing/index.js +11 -0
  52. package/dist/cloud/billing/index.js.map +1 -0
  53. package/dist/cloud/billing/invoice-service.d.ts +57 -0
  54. package/dist/cloud/billing/invoice-service.d.ts.map +1 -0
  55. package/dist/cloud/billing/invoice-service.js +123 -0
  56. package/dist/cloud/billing/invoice-service.js.map +1 -0
  57. package/dist/cloud/billing/plan-management.d.ts +46 -0
  58. package/dist/cloud/billing/plan-management.d.ts.map +1 -0
  59. package/dist/cloud/billing/plan-management.js +157 -0
  60. package/dist/cloud/billing/plan-management.js.map +1 -0
  61. package/dist/cloud/billing/quota-enforcement.d.ts +53 -0
  62. package/dist/cloud/billing/quota-enforcement.d.ts.map +1 -0
  63. package/dist/cloud/billing/quota-enforcement.js +143 -0
  64. package/dist/cloud/billing/quota-enforcement.js.map +1 -0
  65. package/dist/cloud/billing/stripe-client.d.ts +142 -0
  66. package/dist/cloud/billing/stripe-client.d.ts.map +1 -0
  67. package/dist/cloud/billing/stripe-client.js +169 -0
  68. package/dist/cloud/billing/stripe-client.js.map +1 -0
  69. package/dist/cloud/billing/trial-service.d.ts +47 -0
  70. package/dist/cloud/billing/trial-service.d.ts.map +1 -0
  71. package/dist/cloud/billing/trial-service.js +104 -0
  72. package/dist/cloud/billing/trial-service.js.map +1 -0
  73. package/dist/cloud/billing/usage-metering.d.ts +83 -0
  74. package/dist/cloud/billing/usage-metering.d.ts.map +1 -0
  75. package/dist/cloud/billing/usage-metering.js +174 -0
  76. package/dist/cloud/billing/usage-metering.js.map +1 -0
  77. package/dist/cloud/ingestion/backpressure.d.ts +107 -0
  78. package/dist/cloud/ingestion/backpressure.d.ts.map +1 -0
  79. package/dist/cloud/ingestion/backpressure.js +134 -0
  80. package/dist/cloud/ingestion/backpressure.js.map +1 -0
  81. package/dist/cloud/ingestion/batch-writer.d.ts +115 -0
  82. package/dist/cloud/ingestion/batch-writer.d.ts.map +1 -0
  83. package/dist/cloud/ingestion/batch-writer.js +319 -0
  84. package/dist/cloud/ingestion/batch-writer.js.map +1 -0
  85. package/dist/cloud/ingestion/dlq-manager.d.ts +116 -0
  86. package/dist/cloud/ingestion/dlq-manager.d.ts.map +1 -0
  87. package/dist/cloud/ingestion/dlq-manager.js +244 -0
  88. package/dist/cloud/ingestion/dlq-manager.js.map +1 -0
  89. package/dist/cloud/ingestion/event-queue.d.ts +105 -0
  90. package/dist/cloud/ingestion/event-queue.d.ts.map +1 -0
  91. package/dist/cloud/ingestion/event-queue.js +185 -0
  92. package/dist/cloud/ingestion/event-queue.js.map +1 -0
  93. package/dist/cloud/ingestion/gateway.d.ts +68 -0
  94. package/dist/cloud/ingestion/gateway.d.ts.map +1 -0
  95. package/dist/cloud/ingestion/gateway.js +198 -0
  96. package/dist/cloud/ingestion/gateway.js.map +1 -0
  97. package/dist/cloud/ingestion/index.d.ts +7 -0
  98. package/dist/cloud/ingestion/index.d.ts.map +1 -0
  99. package/dist/cloud/ingestion/index.js +7 -0
  100. package/dist/cloud/ingestion/index.js.map +1 -0
  101. package/dist/cloud/ingestion/rate-limiter.d.ts +73 -0
  102. package/dist/cloud/ingestion/rate-limiter.d.ts.map +1 -0
  103. package/dist/cloud/ingestion/rate-limiter.js +153 -0
  104. package/dist/cloud/ingestion/rate-limiter.js.map +1 -0
  105. package/dist/cloud/migrate.d.ts +45 -0
  106. package/dist/cloud/migrate.d.ts.map +1 -0
  107. package/dist/cloud/migrate.js +147 -0
  108. package/dist/cloud/migrate.js.map +1 -0
  109. package/dist/cloud/migration/export-import.d.ts +56 -0
  110. package/dist/cloud/migration/export-import.d.ts.map +1 -0
  111. package/dist/cloud/migration/export-import.js +289 -0
  112. package/dist/cloud/migration/export-import.js.map +1 -0
  113. package/dist/cloud/migration/index.d.ts +5 -0
  114. package/dist/cloud/migration/index.d.ts.map +1 -0
  115. package/dist/cloud/migration/index.js +5 -0
  116. package/dist/cloud/migration/index.js.map +1 -0
  117. package/dist/cloud/org-service.d.ts +68 -0
  118. package/dist/cloud/org-service.d.ts.map +1 -0
  119. package/dist/cloud/org-service.js +169 -0
  120. package/dist/cloud/org-service.js.map +1 -0
  121. package/dist/cloud/partition-maintenance.d.ts +29 -0
  122. package/dist/cloud/partition-maintenance.d.ts.map +1 -0
  123. package/dist/cloud/partition-maintenance.js +96 -0
  124. package/dist/cloud/partition-maintenance.js.map +1 -0
  125. package/dist/cloud/retention/index.d.ts +7 -0
  126. package/dist/cloud/retention/index.d.ts.map +1 -0
  127. package/dist/cloud/retention/index.js +7 -0
  128. package/dist/cloud/retention/index.js.map +1 -0
  129. package/dist/cloud/retention/partition-management.d.ts +61 -0
  130. package/dist/cloud/retention/partition-management.d.ts.map +1 -0
  131. package/dist/cloud/retention/partition-management.js +167 -0
  132. package/dist/cloud/retention/partition-management.js.map +1 -0
  133. package/dist/cloud/retention/retention-job.d.ts +70 -0
  134. package/dist/cloud/retention/retention-job.d.ts.map +1 -0
  135. package/dist/cloud/retention/retention-job.js +160 -0
  136. package/dist/cloud/retention/retention-job.js.map +1 -0
  137. package/dist/cloud/retention/retention-policy.d.ts +27 -0
  138. package/dist/cloud/retention/retention-policy.d.ts.map +1 -0
  139. package/dist/cloud/retention/retention-policy.js +36 -0
  140. package/dist/cloud/retention/retention-policy.js.map +1 -0
  141. package/dist/cloud/routes/api-key-routes.d.ts +38 -0
  142. package/dist/cloud/routes/api-key-routes.d.ts.map +1 -0
  143. package/dist/cloud/routes/api-key-routes.js +84 -0
  144. package/dist/cloud/routes/api-key-routes.js.map +1 -0
  145. package/dist/cloud/routes/audit-routes.d.ts +36 -0
  146. package/dist/cloud/routes/audit-routes.d.ts.map +1 -0
  147. package/dist/cloud/routes/audit-routes.js +47 -0
  148. package/dist/cloud/routes/audit-routes.js.map +1 -0
  149. package/dist/cloud/routes/billing-routes.d.ts +51 -0
  150. package/dist/cloud/routes/billing-routes.d.ts.map +1 -0
  151. package/dist/cloud/routes/billing-routes.js +114 -0
  152. package/dist/cloud/routes/billing-routes.js.map +1 -0
  153. package/dist/cloud/routes/onboarding-routes.d.ts +34 -0
  154. package/dist/cloud/routes/onboarding-routes.d.ts.map +1 -0
  155. package/dist/cloud/routes/onboarding-routes.js +58 -0
  156. package/dist/cloud/routes/onboarding-routes.js.map +1 -0
  157. package/dist/cloud/routes/org-routes.d.ts +80 -0
  158. package/dist/cloud/routes/org-routes.d.ts.map +1 -0
  159. package/dist/cloud/routes/org-routes.js +153 -0
  160. package/dist/cloud/routes/org-routes.js.map +1 -0
  161. package/dist/cloud/routes/usage-routes.d.ts +18 -0
  162. package/dist/cloud/routes/usage-routes.d.ts.map +1 -0
  163. package/dist/cloud/routes/usage-routes.js +66 -0
  164. package/dist/cloud/routes/usage-routes.js.map +1 -0
  165. package/dist/cloud/storage/adapter.d.ts +102 -0
  166. package/dist/cloud/storage/adapter.d.ts.map +1 -0
  167. package/dist/cloud/storage/adapter.js +21 -0
  168. package/dist/cloud/storage/adapter.js.map +1 -0
  169. package/dist/cloud/storage/index.d.ts +8 -0
  170. package/dist/cloud/storage/index.d.ts.map +1 -0
  171. package/dist/cloud/storage/index.js +7 -0
  172. package/dist/cloud/storage/index.js.map +1 -0
  173. package/dist/cloud/storage/postgres-adapter.d.ts +34 -0
  174. package/dist/cloud/storage/postgres-adapter.d.ts.map +1 -0
  175. package/dist/cloud/storage/postgres-adapter.js +544 -0
  176. package/dist/cloud/storage/postgres-adapter.js.map +1 -0
  177. package/dist/cloud/storage/sqlite-adapter.d.ts +29 -0
  178. package/dist/cloud/storage/sqlite-adapter.d.ts.map +1 -0
  179. package/dist/cloud/storage/sqlite-adapter.js +176 -0
  180. package/dist/cloud/storage/sqlite-adapter.js.map +1 -0
  181. package/dist/cloud/tenant-pool.d.ts +49 -0
  182. package/dist/cloud/tenant-pool.d.ts.map +1 -0
  183. package/dist/cloud/tenant-pool.js +61 -0
  184. package/dist/cloud/tenant-pool.js.map +1 -0
  185. 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":"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"}
@@ -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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"}