@revealui/db 0.2.1 → 0.3.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 (213) hide show
  1. package/dist/audit-store.d.ts.map +1 -1
  2. package/dist/audit-store.js.map +1 -1
  3. package/dist/cleanup/cross-db-cleanup.d.ts +63 -0
  4. package/dist/cleanup/cross-db-cleanup.d.ts.map +1 -0
  5. package/dist/cleanup/cross-db-cleanup.js +150 -0
  6. package/dist/cleanup/cross-db-cleanup.js.map +1 -0
  7. package/dist/cleanup/index.d.ts +10 -0
  8. package/dist/cleanup/index.d.ts.map +1 -0
  9. package/dist/cleanup/index.js +10 -0
  10. package/dist/cleanup/index.js.map +1 -0
  11. package/dist/cleanup/stale-tokens.d.ts +37 -0
  12. package/dist/cleanup/stale-tokens.d.ts.map +1 -0
  13. package/dist/cleanup/stale-tokens.js +113 -0
  14. package/dist/cleanup/stale-tokens.js.map +1 -0
  15. package/dist/client/index.d.ts +13 -0
  16. package/dist/client/index.d.ts.map +1 -1
  17. package/dist/client/index.js +46 -7
  18. package/dist/client/index.js.map +1 -1
  19. package/dist/client/types.d.ts.map +1 -1
  20. package/dist/crypto.js.map +1 -1
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +4 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/log-transport.d.ts.map +1 -1
  26. package/dist/log-transport.js +3 -1
  27. package/dist/log-transport.js.map +1 -1
  28. package/dist/pool.d.ts +0 -3
  29. package/dist/pool.d.ts.map +1 -1
  30. package/dist/pool.js +13 -5
  31. package/dist/pool.js.map +1 -1
  32. package/dist/queries/boards.d.ts +13 -1
  33. package/dist/queries/boards.d.ts.map +1 -1
  34. package/dist/queries/boards.js +5 -2
  35. package/dist/queries/boards.js.map +1 -1
  36. package/dist/queries/code-provenance.d.ts.map +1 -1
  37. package/dist/queries/code-provenance.js.map +1 -1
  38. package/dist/queries/media.d.ts +9 -0
  39. package/dist/queries/media.d.ts.map +1 -1
  40. package/dist/queries/media.js +23 -13
  41. package/dist/queries/media.js.map +1 -1
  42. package/dist/queries/pages.d.ts +10 -0
  43. package/dist/queries/pages.d.ts.map +1 -1
  44. package/dist/queries/pages.js +24 -5
  45. package/dist/queries/pages.js.map +1 -1
  46. package/dist/queries/posts.d.ts +10 -0
  47. package/dist/queries/posts.d.ts.map +1 -1
  48. package/dist/queries/posts.js +17 -5
  49. package/dist/queries/posts.js.map +1 -1
  50. package/dist/queries/sites.d.ts +36 -1
  51. package/dist/queries/sites.d.ts.map +1 -1
  52. package/dist/queries/sites.js +45 -5
  53. package/dist/queries/sites.js.map +1 -1
  54. package/dist/queries/ticket-comments.d.ts +9 -1
  55. package/dist/queries/ticket-comments.d.ts.map +1 -1
  56. package/dist/queries/ticket-comments.js +4 -0
  57. package/dist/queries/ticket-comments.js.map +1 -1
  58. package/dist/queries/ticket-labels.d.ts +10 -0
  59. package/dist/queries/ticket-labels.d.ts.map +1 -1
  60. package/dist/queries/ticket-labels.js +4 -0
  61. package/dist/queries/ticket-labels.js.map +1 -1
  62. package/dist/queries/tickets.d.ts +1 -1
  63. package/dist/queries/tickets.d.ts.map +1 -1
  64. package/dist/queries/tickets.js +4 -7
  65. package/dist/queries/tickets.js.map +1 -1
  66. package/dist/queries/users.d.ts +109 -0
  67. package/dist/queries/users.d.ts.map +1 -0
  68. package/dist/queries/users.js +44 -0
  69. package/dist/queries/users.js.map +1 -0
  70. package/dist/schema/accounts.d.ts +962 -0
  71. package/dist/schema/accounts.d.ts.map +1 -0
  72. package/dist/schema/accounts.js +115 -0
  73. package/dist/schema/accounts.js.map +1 -0
  74. package/dist/schema/agents.d.ts +165 -1
  75. package/dist/schema/agents.d.ts.map +1 -1
  76. package/dist/schema/agents.js +56 -5
  77. package/dist/schema/agents.js.map +1 -1
  78. package/dist/schema/api-keys.d.ts +17 -0
  79. package/dist/schema/api-keys.d.ts.map +1 -1
  80. package/dist/schema/api-keys.js +3 -0
  81. package/dist/schema/api-keys.js.map +1 -1
  82. package/dist/schema/app-logs.d.ts.map +1 -1
  83. package/dist/schema/app-logs.js.map +1 -1
  84. package/dist/schema/audit-log.d.ts.map +1 -1
  85. package/dist/schema/audit-log.js.map +1 -1
  86. package/dist/schema/cms.d.ts +68 -0
  87. package/dist/schema/cms.d.ts.map +1 -1
  88. package/dist/schema/cms.js +18 -4
  89. package/dist/schema/cms.js.map +1 -1
  90. package/dist/schema/code-provenance.d.ts.map +1 -1
  91. package/dist/schema/code-provenance.js.map +1 -1
  92. package/dist/schema/collab-edits.d.ts.map +1 -1
  93. package/dist/schema/collab-edits.js +2 -2
  94. package/dist/schema/collab-edits.js.map +1 -1
  95. package/dist/schema/coordination.d.ts +967 -0
  96. package/dist/schema/coordination.d.ts.map +1 -0
  97. package/dist/schema/coordination.js +109 -0
  98. package/dist/schema/coordination.js.map +1 -0
  99. package/dist/schema/crdt-operations.d.ts.map +1 -1
  100. package/dist/schema/crdt-operations.js.map +1 -1
  101. package/dist/schema/error-events.d.ts.map +1 -1
  102. package/dist/schema/error-events.js.map +1 -1
  103. package/dist/schema/gdpr.d.ts +529 -0
  104. package/dist/schema/gdpr.d.ts.map +1 -0
  105. package/dist/schema/gdpr.js +93 -0
  106. package/dist/schema/gdpr.js.map +1 -0
  107. package/dist/schema/index.d.ts +37 -1
  108. package/dist/schema/index.d.ts.map +1 -1
  109. package/dist/schema/index.js +73 -0
  110. package/dist/schema/index.js.map +1 -1
  111. package/dist/schema/jobs.d.ts +242 -0
  112. package/dist/schema/jobs.d.ts.map +1 -0
  113. package/dist/schema/jobs.js +48 -0
  114. package/dist/schema/jobs.js.map +1 -0
  115. package/dist/schema/licenses.d.ts +51 -0
  116. package/dist/schema/licenses.d.ts.map +1 -1
  117. package/dist/schema/licenses.js +8 -2
  118. package/dist/schema/licenses.js.map +1 -1
  119. package/dist/schema/magic-links.d.ts +136 -0
  120. package/dist/schema/magic-links.d.ts.map +1 -0
  121. package/dist/schema/magic-links.js +32 -0
  122. package/dist/schema/magic-links.js.map +1 -0
  123. package/dist/schema/marketplace.d.ts +496 -0
  124. package/dist/schema/marketplace.d.ts.map +1 -0
  125. package/dist/schema/marketplace.js +110 -0
  126. package/dist/schema/marketplace.js.map +1 -0
  127. package/dist/schema/node-ids.d.ts.map +1 -1
  128. package/dist/schema/node-ids.js.map +1 -1
  129. package/dist/schema/oauth-accounts.d.ts +34 -0
  130. package/dist/schema/oauth-accounts.d.ts.map +1 -1
  131. package/dist/schema/oauth-accounts.js +5 -0
  132. package/dist/schema/oauth-accounts.js.map +1 -1
  133. package/dist/schema/pages.d.ts +34 -0
  134. package/dist/schema/pages.d.ts.map +1 -1
  135. package/dist/schema/pages.js +14 -4
  136. package/dist/schema/pages.js.map +1 -1
  137. package/dist/schema/passkeys.d.ts +208 -0
  138. package/dist/schema/passkeys.d.ts.map +1 -0
  139. package/dist/schema/passkeys.js +47 -0
  140. package/dist/schema/passkeys.js.map +1 -0
  141. package/dist/schema/password-reset-tokens.d.ts.map +1 -1
  142. package/dist/schema/password-reset-tokens.js +5 -2
  143. package/dist/schema/password-reset-tokens.js.map +1 -1
  144. package/dist/schema/rag.d.ts.map +1 -1
  145. package/dist/schema/rag.js +5 -2
  146. package/dist/schema/rag.js.map +1 -1
  147. package/dist/schema/rate-limits.d.ts.map +1 -1
  148. package/dist/schema/rate-limits.js +5 -2
  149. package/dist/schema/rate-limits.js.map +1 -1
  150. package/dist/schema/rest.d.ts +9 -2
  151. package/dist/schema/rest.d.ts.map +1 -1
  152. package/dist/schema/rest.js +9 -2
  153. package/dist/schema/rest.js.map +1 -1
  154. package/dist/schema/sites.d.ts +34 -0
  155. package/dist/schema/sites.d.ts.map +1 -1
  156. package/dist/schema/sites.js +15 -3
  157. package/dist/schema/sites.js.map +1 -1
  158. package/dist/schema/tenants.d.ts +188 -0
  159. package/dist/schema/tenants.d.ts.map +1 -0
  160. package/dist/schema/tenants.js +15 -0
  161. package/dist/schema/tenants.js.map +1 -0
  162. package/dist/schema/tickets.d.ts.map +1 -1
  163. package/dist/schema/tickets.js +17 -5
  164. package/dist/schema/tickets.js.map +1 -1
  165. package/dist/schema/users.d.ts +174 -0
  166. package/dist/schema/users.d.ts.map +1 -1
  167. package/dist/schema/users.js +32 -3
  168. package/dist/schema/users.js.map +1 -1
  169. package/dist/schema/vector.d.ts.map +1 -1
  170. package/dist/schema/vector.js.map +1 -1
  171. package/dist/schema/waitlist.d.ts.map +1 -1
  172. package/dist/schema/waitlist.js.map +1 -1
  173. package/dist/schema/webhook-events.d.ts.map +1 -1
  174. package/dist/schema/webhook-events.js.map +1 -1
  175. package/dist/schema/yjs-documents.d.ts.map +1 -1
  176. package/dist/schema/yjs-documents.js.map +1 -1
  177. package/dist/scripts/cleanup-expired.d.ts +12 -0
  178. package/dist/scripts/cleanup-expired.d.ts.map +1 -0
  179. package/dist/scripts/cleanup-expired.js +50 -0
  180. package/dist/scripts/cleanup-expired.js.map +1 -0
  181. package/dist/security-audit-storage.d.ts +54 -0
  182. package/dist/security-audit-storage.d.ts.map +1 -0
  183. package/dist/security-audit-storage.js +100 -0
  184. package/dist/security-audit-storage.js.map +1 -0
  185. package/dist/types/database.d.ts +320 -2
  186. package/dist/types/database.d.ts.map +1 -1
  187. package/dist/types/database.js +66 -1
  188. package/dist/types/database.js.map +1 -1
  189. package/dist/types/discover.d.ts +2 -2
  190. package/dist/types/discover.d.ts.map +1 -1
  191. package/dist/types/discover.js +16 -16
  192. package/dist/types/discover.js.map +1 -1
  193. package/dist/types/extract-relationships.d.ts.map +1 -1
  194. package/dist/types/extract-relationships.js.map +1 -1
  195. package/dist/types/generate-contracts.js.map +1 -1
  196. package/dist/types/generate-zod-schemas.js.map +1 -1
  197. package/dist/types/generate.js.map +1 -1
  198. package/dist/types/index.d.ts.map +1 -1
  199. package/dist/types/introspect.d.ts.map +1 -1
  200. package/dist/types/introspect.js +0 -1
  201. package/dist/types/introspect.js.map +1 -1
  202. package/dist/types/stripe-schema.d.ts.map +1 -1
  203. package/dist/types/stripe-schema.js +7 -2
  204. package/dist/types/stripe-schema.js.map +1 -1
  205. package/dist/utils/soft-delete.d.ts +45 -0
  206. package/dist/utils/soft-delete.d.ts.map +1 -0
  207. package/dist/utils/soft-delete.js +45 -0
  208. package/dist/utils/soft-delete.js.map +1 -0
  209. package/dist/validation/cross-db.d.ts +60 -0
  210. package/dist/validation/cross-db.d.ts.map +1 -0
  211. package/dist/validation/cross-db.js +146 -0
  212. package/dist/validation/cross-db.js.map +1 -0
  213. package/package.json +25 -6
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Soft-delete query helpers for Drizzle ORM.
3
+ *
4
+ * Tables with a `deletedAt` column use soft-delete: rows are never removed,
5
+ * only marked with a timestamp. These helpers ensure queries consistently
6
+ * filter out soft-deleted rows, preventing accidental data leaks.
7
+ *
8
+ * Usage:
9
+ * ```typescript
10
+ * import { whereActive, withActiveFilter } from '@revealui/db/utils/soft-delete'
11
+ * import { users } from '@revealui/db/schema'
12
+ *
13
+ * // Simple: only active users
14
+ * const active = await db.select().from(users).where(whereActive(users))
15
+ *
16
+ * // Combined with other filters
17
+ * const admins = await db.select().from(users).where(
18
+ * withActiveFilter(users, eq(users.role, 'admin'))
19
+ * )
20
+ * ```
21
+ */
22
+ import type { Column } from 'drizzle-orm';
23
+ import { type SQL } from 'drizzle-orm';
24
+ /** Any table that has a deletedAt column */
25
+ interface SoftDeletable {
26
+ deletedAt: Column;
27
+ }
28
+ /**
29
+ * Returns a SQL condition that filters to only non-deleted rows.
30
+ *
31
+ * @param table - A Drizzle table with a `deletedAt` column
32
+ * @returns SQL condition: `deleted_at IS NULL`
33
+ */
34
+ export declare function whereActive<T extends SoftDeletable>(table: T): SQL;
35
+ /**
36
+ * Combines an existing WHERE clause with the soft-delete active filter.
37
+ * If no existing clause is provided, returns just the active filter.
38
+ *
39
+ * @param table - A Drizzle table with a `deletedAt` column
40
+ * @param existingWhere - Optional existing WHERE condition to combine with
41
+ * @returns Combined SQL condition: `(existingWhere) AND deleted_at IS NULL`
42
+ */
43
+ export declare function withActiveFilter<T extends SoftDeletable>(table: T, existingWhere?: SQL): SQL;
44
+ export {};
45
+ //# sourceMappingURL=soft-delete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"soft-delete.d.ts","sourceRoot":"","sources":["../../src/utils/soft-delete.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAe,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC;AAEpD,4CAA4C;AAC5C,UAAU,aAAa;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,aAAa,EAAE,KAAK,EAAE,CAAC,GAAG,GAAG,CAElE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,aAAa,EAAE,KAAK,EAAE,CAAC,EAAE,aAAa,CAAC,EAAE,GAAG,GAAG,GAAG,CAI5F"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Soft-delete query helpers for Drizzle ORM.
3
+ *
4
+ * Tables with a `deletedAt` column use soft-delete: rows are never removed,
5
+ * only marked with a timestamp. These helpers ensure queries consistently
6
+ * filter out soft-deleted rows, preventing accidental data leaks.
7
+ *
8
+ * Usage:
9
+ * ```typescript
10
+ * import { whereActive, withActiveFilter } from '@revealui/db/utils/soft-delete'
11
+ * import { users } from '@revealui/db/schema'
12
+ *
13
+ * // Simple: only active users
14
+ * const active = await db.select().from(users).where(whereActive(users))
15
+ *
16
+ * // Combined with other filters
17
+ * const admins = await db.select().from(users).where(
18
+ * withActiveFilter(users, eq(users.role, 'admin'))
19
+ * )
20
+ * ```
21
+ */
22
+ import { and, isNull } from 'drizzle-orm';
23
+ /**
24
+ * Returns a SQL condition that filters to only non-deleted rows.
25
+ *
26
+ * @param table - A Drizzle table with a `deletedAt` column
27
+ * @returns SQL condition: `deleted_at IS NULL`
28
+ */
29
+ export function whereActive(table) {
30
+ return isNull(table.deletedAt);
31
+ }
32
+ /**
33
+ * Combines an existing WHERE clause with the soft-delete active filter.
34
+ * If no existing clause is provided, returns just the active filter.
35
+ *
36
+ * @param table - A Drizzle table with a `deletedAt` column
37
+ * @param existingWhere - Optional existing WHERE condition to combine with
38
+ * @returns Combined SQL condition: `(existingWhere) AND deleted_at IS NULL`
39
+ */
40
+ export function withActiveFilter(table, existingWhere) {
41
+ const activeFilter = isNull(table.deletedAt);
42
+ // biome-ignore lint/style/noNonNullAssertion: and() with two defined args always returns SQL
43
+ return existingWhere ? and(existingWhere, activeFilter) : activeFilter;
44
+ }
45
+ //# sourceMappingURL=soft-delete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"soft-delete.js","sourceRoot":"","sources":["../../src/utils/soft-delete.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAE,GAAG,EAAE,MAAM,EAAY,MAAM,aAAa,CAAC;AAOpD;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAA0B,KAAQ;IAC3D,OAAO,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAA0B,KAAQ,EAAE,aAAmB;IACrF,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC7C,6FAA6F;IAC7F,OAAO,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,EAAE,YAAY,CAAE,CAAC,CAAC,CAAC,YAAY,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Cross-Database Referential Integrity Validation
3
+ *
4
+ * Supabase (vector DB) tables reference NeonDB (REST DB) entities via
5
+ * text IDs (siteId, userId). PostgreSQL cannot enforce FK constraints
6
+ * across separate database instances, so application-level validation
7
+ * is required before writing cross-DB references.
8
+ *
9
+ * Affected Supabase tables:
10
+ * - agentMemories.siteId → sites.id (NeonDB)
11
+ * - agentMemories.verifiedBy → users.id (NeonDB)
12
+ * - ragDocuments.workspaceId → sites.id (NeonDB)
13
+ * - ragChunks.workspaceId → sites.id (NeonDB)
14
+ */
15
+ import type { DatabaseClient } from '../client/types.js';
16
+ export declare class CrossDbReferenceError extends Error {
17
+ readonly table: string;
18
+ readonly column: string;
19
+ readonly referencedId: string;
20
+ constructor(table: string, column: string, referencedId: string);
21
+ }
22
+ /**
23
+ * Validates that a site exists in the REST database (NeonDB).
24
+ * Call this before inserting into Supabase tables that reference sites.id.
25
+ */
26
+ export declare function validateSiteExists(restDb: DatabaseClient, siteId: string): Promise<boolean>;
27
+ /**
28
+ * Validates that a user exists in the REST database (NeonDB).
29
+ * Call this before inserting into Supabase tables that reference users.id.
30
+ */
31
+ export declare function validateUserExists(restDb: DatabaseClient, userId: string): Promise<boolean>;
32
+ /**
33
+ * Validates cross-DB references and throws if any are invalid.
34
+ * Use before inserting agent memories, RAG documents, etc. into Supabase.
35
+ */
36
+ export declare function assertCrossDbRefs(restDb: DatabaseClient, refs: {
37
+ siteId?: string;
38
+ userId?: string;
39
+ }): Promise<void>;
40
+ /**
41
+ * Insert into vector DB with cross-DB reference validation.
42
+ * Validates FK references exist in REST DB before insert, retries once
43
+ * on failure after re-validating references.
44
+ */
45
+ export declare function safeVectorInsert<T>(restDb: DatabaseClient, insert: () => Promise<T>, refs: {
46
+ siteId?: string;
47
+ userId?: string;
48
+ }): Promise<T>;
49
+ /**
50
+ * Finds orphaned records in Supabase that reference non-existent NeonDB entities.
51
+ * Returns IDs of orphaned records grouped by table.
52
+ *
53
+ * This is a diagnostic/cleanup utility — run periodically or on-demand.
54
+ * Requires both REST and Vector database clients.
55
+ */
56
+ export declare function findOrphanedMemories(restDb: DatabaseClient, vectorDb: DatabaseClient): Promise<{
57
+ orphanedBySite: string[];
58
+ orphanedByUser: string[];
59
+ }>;
60
+ //# sourceMappingURL=cross-db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-db.d.ts","sourceRoot":"","sources":["../../src/validation/cross-db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIzD,qBAAa,qBAAsB,SAAQ,KAAK;aAE5B,KAAK,EAAE,MAAM;aACb,MAAM,EAAE,MAAM;aACd,YAAY,EAAE,MAAM;gBAFpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM;CAKvC;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOjG;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOjG;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE;IACJ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACA,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACxB,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACzC,OAAO,CAAC,CAAC,CAAC,CAiBZ;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC;IACT,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC,CA6CD"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Cross-Database Referential Integrity Validation
3
+ *
4
+ * Supabase (vector DB) tables reference NeonDB (REST DB) entities via
5
+ * text IDs (siteId, userId). PostgreSQL cannot enforce FK constraints
6
+ * across separate database instances, so application-level validation
7
+ * is required before writing cross-DB references.
8
+ *
9
+ * Affected Supabase tables:
10
+ * - agentMemories.siteId → sites.id (NeonDB)
11
+ * - agentMemories.verifiedBy → users.id (NeonDB)
12
+ * - ragDocuments.workspaceId → sites.id (NeonDB)
13
+ * - ragChunks.workspaceId → sites.id (NeonDB)
14
+ */
15
+ import { eq, inArray } from 'drizzle-orm';
16
+ import { sites } from '../schema/sites.js';
17
+ import { users } from '../schema/users.js';
18
+ export class CrossDbReferenceError extends Error {
19
+ table;
20
+ column;
21
+ referencedId;
22
+ constructor(table, column, referencedId) {
23
+ super(`Cross-DB reference violation: ${table}.${column} = '${referencedId}' does not exist`);
24
+ this.table = table;
25
+ this.column = column;
26
+ this.referencedId = referencedId;
27
+ this.name = 'CrossDbReferenceError';
28
+ }
29
+ }
30
+ /**
31
+ * Validates that a site exists in the REST database (NeonDB).
32
+ * Call this before inserting into Supabase tables that reference sites.id.
33
+ */
34
+ export async function validateSiteExists(restDb, siteId) {
35
+ const result = await restDb
36
+ .select({ id: sites.id })
37
+ .from(sites)
38
+ .where(eq(sites.id, siteId))
39
+ .limit(1);
40
+ return result.length > 0;
41
+ }
42
+ /**
43
+ * Validates that a user exists in the REST database (NeonDB).
44
+ * Call this before inserting into Supabase tables that reference users.id.
45
+ */
46
+ export async function validateUserExists(restDb, userId) {
47
+ const result = await restDb
48
+ .select({ id: users.id })
49
+ .from(users)
50
+ .where(eq(users.id, userId))
51
+ .limit(1);
52
+ return result.length > 0;
53
+ }
54
+ /**
55
+ * Validates cross-DB references and throws if any are invalid.
56
+ * Use before inserting agent memories, RAG documents, etc. into Supabase.
57
+ */
58
+ export async function assertCrossDbRefs(restDb, refs) {
59
+ const checks = [];
60
+ if (refs.siteId) {
61
+ const siteId = refs.siteId;
62
+ checks.push(validateSiteExists(restDb, siteId).then((exists) => {
63
+ if (!exists) {
64
+ throw new CrossDbReferenceError('supabase_table', 'siteId', siteId);
65
+ }
66
+ }));
67
+ }
68
+ if (refs.userId) {
69
+ const userId = refs.userId;
70
+ checks.push(validateUserExists(restDb, userId).then((exists) => {
71
+ if (!exists) {
72
+ throw new CrossDbReferenceError('supabase_table', 'userId', userId);
73
+ }
74
+ }));
75
+ }
76
+ await Promise.all(checks);
77
+ }
78
+ /**
79
+ * Insert into vector DB with cross-DB reference validation.
80
+ * Validates FK references exist in REST DB before insert, retries once
81
+ * on failure after re-validating references.
82
+ */
83
+ export async function safeVectorInsert(restDb, insert, refs) {
84
+ // Pre-validate references
85
+ await assertCrossDbRefs(restDb, refs);
86
+ try {
87
+ return await insert();
88
+ }
89
+ catch (error) {
90
+ // Re-validate references — if entity was deleted, throw descriptive error
91
+ const refsValid = await assertCrossDbRefs(restDb, refs)
92
+ .then(() => true)
93
+ .catch(() => false);
94
+ if (!refsValid) {
95
+ throw new CrossDbReferenceError('vector_table', 'refs', JSON.stringify(refs));
96
+ }
97
+ // References still valid — rethrow original error
98
+ throw error;
99
+ }
100
+ }
101
+ /**
102
+ * Finds orphaned records in Supabase that reference non-existent NeonDB entities.
103
+ * Returns IDs of orphaned records grouped by table.
104
+ *
105
+ * This is a diagnostic/cleanup utility — run periodically or on-demand.
106
+ * Requires both REST and Vector database clients.
107
+ */
108
+ export async function findOrphanedMemories(restDb, vectorDb) {
109
+ // Import dynamically to avoid circular deps at module scope
110
+ const { agentMemories } = await import('../schema/agents.js');
111
+ // Get all distinct siteIds and verifiedBy from agent memories
112
+ const memories = await vectorDb
113
+ .select({
114
+ id: agentMemories.id,
115
+ siteId: agentMemories.siteId,
116
+ verifiedBy: agentMemories.verifiedBy,
117
+ })
118
+ .from(agentMemories);
119
+ const orphanedBySite = [];
120
+ const orphanedByUser = [];
121
+ // Batch-check site existence (single query instead of N+1)
122
+ const uniqueSiteIds = [...new Set(memories.map((m) => m.siteId))];
123
+ const existingSiteRows = uniqueSiteIds.length > 0
124
+ ? await restDb.select({ id: sites.id }).from(sites).where(inArray(sites.id, uniqueSiteIds))
125
+ : [];
126
+ const existingSites = new Set(existingSiteRows.map((r) => r.id));
127
+ // Batch-check user existence (single query instead of N+1)
128
+ const uniqueUserIds = [
129
+ ...new Set(memories.map((m) => m.verifiedBy).filter((id) => id != null)),
130
+ ];
131
+ const existingUserRows = uniqueUserIds.length > 0
132
+ ? await restDb.select({ id: users.id }).from(users).where(inArray(users.id, uniqueUserIds))
133
+ : [];
134
+ const existingUsers = new Set(existingUserRows.map((r) => r.id));
135
+ // Find orphans
136
+ for (const memory of memories) {
137
+ if (!existingSites.has(memory.siteId)) {
138
+ orphanedBySite.push(memory.id);
139
+ }
140
+ if (memory.verifiedBy && !existingUsers.has(memory.verifiedBy)) {
141
+ orphanedByUser.push(memory.id);
142
+ }
143
+ }
144
+ return { orphanedBySite, orphanedByUser };
145
+ }
146
+ //# sourceMappingURL=cross-db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-db.js","sourceRoot":"","sources":["../../src/validation/cross-db.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revealui/db",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "dist"
@@ -10,9 +10,9 @@
10
10
  "drizzle-orm": "^0.45.1",
11
11
  "drizzle-zod": "^0.8.3",
12
12
  "pg": "^8.18.0",
13
- "zod": "^4.3.5",
14
- "@revealui/config": "0.2.0",
15
- "@revealui/utils": "0.2.0"
13
+ "zod": "^4.3.6",
14
+ "@revealui/config": "0.3.0",
15
+ "@revealui/utils": "0.3.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "drizzle-kit": "^0.31.8",
@@ -20,6 +20,9 @@
20
20
  "typescript": "^5.9.3",
21
21
  "dev": "0.0.1"
22
22
  },
23
+ "engines": {
24
+ "node": ">=24.13.0"
25
+ },
23
26
  "exports": {
24
27
  ".": {
25
28
  "types": "./dist/index.d.ts",
@@ -37,6 +40,14 @@
37
40
  "types": "./dist/client/index.d.ts",
38
41
  "import": "./dist/client/index.js"
39
42
  },
43
+ "./validation": {
44
+ "types": "./dist/validation/cross-db.d.ts",
45
+ "import": "./dist/validation/cross-db.js"
46
+ },
47
+ "./cleanup": {
48
+ "types": "./dist/cleanup/index.d.ts",
49
+ "import": "./dist/cleanup/index.js"
50
+ },
40
51
  "./types": {
41
52
  "types": "./dist/types/index.d.ts",
42
53
  "import": "./dist/types/index.js"
@@ -69,6 +80,10 @@
69
80
  "types": "./dist/schema/sites.d.ts",
70
81
  "import": "./dist/schema/sites.js"
71
82
  },
83
+ "./schema/tenants": {
84
+ "types": "./dist/schema/tenants.d.ts",
85
+ "import": "./dist/schema/tenants.js"
86
+ },
72
87
  "./schema/users": {
73
88
  "types": "./dist/schema/users.d.ts",
74
89
  "import": "./dist/schema/users.js"
@@ -144,6 +159,10 @@
144
159
  "./crypto": {
145
160
  "types": "./dist/crypto.d.ts",
146
161
  "import": "./dist/crypto.js"
162
+ },
163
+ "./validation/cross-db": {
164
+ "types": "./dist/validation/cross-db.d.ts",
165
+ "import": "./dist/validation/cross-db.js"
147
166
  }
148
167
  },
149
168
  "main": "./dist/index.js",
@@ -156,6 +175,7 @@
156
175
  "scripts": {
157
176
  "build": "tsc",
158
177
  "clean": "rm -rf dist",
178
+ "db:cleanup": "tsx src/scripts/cleanup-expired.ts",
159
179
  "db:generate": "drizzle-kit generate",
160
180
  "db:migrate": "drizzle-kit migrate",
161
181
  "db:push": "drizzle-kit push",
@@ -166,9 +186,8 @@
166
186
  "generate:types": "tsx src/types/generate.ts",
167
187
  "generate:zod": "tsx src/types/generate-zod-schemas.ts",
168
188
  "lint": "biome check .",
169
- "lint:eslint": "eslint .",
170
189
  "test": "vitest run",
171
- "test:coverage": "vitest run --coverage",
190
+ "test:coverage": "vitest run --coverage --coverage.reporter=json-summary --coverage.reporter=html --coverage.reporter=text",
172
191
  "typecheck": "tsc --noEmit"
173
192
  }
174
193
  }