@aphexcms/cms-core 9.8.1 → 9.9.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 (226) hide show
  1. package/dist/api/organizations.d.ts +2 -1
  2. package/dist/api/organizations.d.ts.map +1 -1
  3. package/dist/api/organizations.js +2 -1
  4. package/dist/api/schemas/jobs.d.ts +15 -0
  5. package/dist/api/schemas/jobs.d.ts.map +1 -0
  6. package/dist/api/schemas/jobs.js +17 -0
  7. package/dist/api/schemas/user.d.ts.map +1 -1
  8. package/dist/api/schemas/user.js +6 -2
  9. package/dist/auth/account-deletion.d.ts +35 -0
  10. package/dist/auth/account-deletion.d.ts.map +1 -0
  11. package/dist/auth/account-deletion.js +65 -0
  12. package/dist/auth/bootstrap.d.ts +104 -0
  13. package/dist/auth/bootstrap.d.ts.map +1 -0
  14. package/dist/auth/bootstrap.js +164 -0
  15. package/dist/auth/instance-state.d.ts +13 -0
  16. package/dist/auth/instance-state.d.ts.map +1 -0
  17. package/dist/auth/instance-state.js +30 -0
  18. package/dist/auth/invitation-status.d.ts +25 -0
  19. package/dist/auth/invitation-status.d.ts.map +1 -0
  20. package/dist/auth/invitation-status.js +41 -0
  21. package/dist/auth/sign-up-policy.d.ts +62 -0
  22. package/dist/auth/sign-up-policy.d.ts.map +1 -0
  23. package/dist/auth/sign-up-policy.js +120 -0
  24. package/dist/components/admin/ActivityView.svelte +263 -13
  25. package/dist/components/admin/ActivityView.svelte.d.ts +10 -1
  26. package/dist/components/admin/ActivityView.svelte.d.ts.map +1 -1
  27. package/dist/db/interfaces/events.d.ts +29 -2
  28. package/dist/db/interfaces/events.d.ts.map +1 -1
  29. package/dist/db/interfaces/index.d.ts +1 -0
  30. package/dist/db/interfaces/index.d.ts.map +1 -1
  31. package/dist/db/interfaces/index.js +1 -0
  32. package/dist/db/interfaces/instance.d.ts +29 -0
  33. package/dist/db/interfaces/instance.d.ts.map +1 -1
  34. package/dist/db/interfaces/instance.js +8 -1
  35. package/dist/events/built-in-consumers.d.ts +20 -0
  36. package/dist/events/built-in-consumers.d.ts.map +1 -0
  37. package/dist/events/built-in-consumers.js +49 -0
  38. package/dist/events/catalog.d.ts +16 -0
  39. package/dist/events/catalog.d.ts.map +1 -1
  40. package/dist/events/catalog.js +17 -0
  41. package/dist/events/consumer.d.ts +14 -0
  42. package/dist/events/consumer.d.ts.map +1 -1
  43. package/dist/events/consumer.js +1 -0
  44. package/dist/events/emit.d.ts +23 -0
  45. package/dist/events/emit.d.ts.map +1 -1
  46. package/dist/events/emit.js +32 -1
  47. package/dist/events/index.d.ts +2 -0
  48. package/dist/events/index.d.ts.map +1 -1
  49. package/dist/events/index.js +2 -0
  50. package/dist/field-validation/utils.d.ts +27 -4
  51. package/dist/field-validation/utils.d.ts.map +1 -1
  52. package/dist/field-validation/utils.js +78 -10
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +3 -0
  56. package/dist/jobs/run-batch.d.ts +7 -1
  57. package/dist/jobs/run-batch.d.ts.map +1 -1
  58. package/dist/jobs/run-batch.js +3 -1
  59. package/dist/lib/api/organizations.d.ts +2 -1
  60. package/dist/lib/api/organizations.d.ts.map +1 -1
  61. package/dist/lib/api/organizations.js +2 -1
  62. package/dist/lib/api/organizations.js.map +1 -1
  63. package/dist/lib/api/schemas/api-keys.d.ts +4 -4
  64. package/dist/lib/api/schemas/jobs.d.ts +15 -0
  65. package/dist/lib/api/schemas/jobs.d.ts.map +1 -0
  66. package/dist/lib/api/schemas/jobs.js +18 -0
  67. package/dist/lib/api/schemas/jobs.js.map +1 -0
  68. package/dist/lib/api/schemas/user.d.ts.map +1 -1
  69. package/dist/lib/api/schemas/user.js +6 -2
  70. package/dist/lib/api/schemas/user.js.map +1 -1
  71. package/dist/lib/auth/account-deletion.d.ts +35 -0
  72. package/dist/lib/auth/account-deletion.d.ts.map +1 -0
  73. package/dist/lib/auth/account-deletion.js +66 -0
  74. package/dist/lib/auth/account-deletion.js.map +1 -0
  75. package/dist/lib/auth/bootstrap.d.ts +104 -0
  76. package/dist/lib/auth/bootstrap.d.ts.map +1 -0
  77. package/dist/lib/auth/bootstrap.js +165 -0
  78. package/dist/lib/auth/bootstrap.js.map +1 -0
  79. package/dist/lib/auth/instance-state.d.ts +13 -0
  80. package/dist/lib/auth/instance-state.d.ts.map +1 -0
  81. package/dist/lib/auth/instance-state.js +31 -0
  82. package/dist/lib/auth/instance-state.js.map +1 -0
  83. package/dist/lib/auth/invitation-status.d.ts +25 -0
  84. package/dist/lib/auth/invitation-status.d.ts.map +1 -0
  85. package/dist/lib/auth/invitation-status.js +42 -0
  86. package/dist/lib/auth/invitation-status.js.map +1 -0
  87. package/dist/lib/auth/sign-up-policy.d.ts +62 -0
  88. package/dist/lib/auth/sign-up-policy.d.ts.map +1 -0
  89. package/dist/lib/auth/sign-up-policy.js +121 -0
  90. package/dist/lib/auth/sign-up-policy.js.map +1 -0
  91. package/dist/lib/db/interfaces/events.d.ts +29 -2
  92. package/dist/lib/db/interfaces/events.d.ts.map +1 -1
  93. package/dist/lib/db/interfaces/index.d.ts +1 -0
  94. package/dist/lib/db/interfaces/index.d.ts.map +1 -1
  95. package/dist/lib/db/interfaces/index.js +1 -0
  96. package/dist/lib/db/interfaces/index.js.map +1 -1
  97. package/dist/lib/db/interfaces/instance.d.ts +29 -0
  98. package/dist/lib/db/interfaces/instance.d.ts.map +1 -1
  99. package/dist/lib/db/interfaces/instance.js +8 -1
  100. package/dist/lib/db/interfaces/instance.js.map +1 -1
  101. package/dist/lib/events/built-in-consumers.d.ts +20 -0
  102. package/dist/lib/events/built-in-consumers.d.ts.map +1 -0
  103. package/dist/lib/events/built-in-consumers.js +50 -0
  104. package/dist/lib/events/built-in-consumers.js.map +1 -0
  105. package/dist/lib/events/catalog.d.ts +16 -0
  106. package/dist/lib/events/catalog.d.ts.map +1 -1
  107. package/dist/lib/events/catalog.js +17 -0
  108. package/dist/lib/events/catalog.js.map +1 -1
  109. package/dist/lib/events/consumer.d.ts +14 -0
  110. package/dist/lib/events/consumer.d.ts.map +1 -1
  111. package/dist/lib/events/consumer.js +1 -0
  112. package/dist/lib/events/consumer.js.map +1 -1
  113. package/dist/lib/events/emit.d.ts +23 -0
  114. package/dist/lib/events/emit.d.ts.map +1 -1
  115. package/dist/lib/events/emit.js +32 -1
  116. package/dist/lib/events/emit.js.map +1 -1
  117. package/dist/lib/events/index.d.ts +2 -0
  118. package/dist/lib/events/index.d.ts.map +1 -1
  119. package/dist/lib/events/index.js +2 -0
  120. package/dist/lib/events/index.js.map +1 -1
  121. package/dist/lib/field-validation/utils.d.ts +27 -4
  122. package/dist/lib/field-validation/utils.d.ts.map +1 -1
  123. package/dist/lib/field-validation/utils.js +78 -10
  124. package/dist/lib/field-validation/utils.js.map +1 -1
  125. package/dist/lib/index.d.ts +1 -0
  126. package/dist/lib/index.d.ts.map +1 -1
  127. package/dist/lib/index.js +3 -0
  128. package/dist/lib/index.js.map +1 -1
  129. package/dist/lib/jobs/run-batch.d.ts +7 -1
  130. package/dist/lib/jobs/run-batch.d.ts.map +1 -1
  131. package/dist/lib/jobs/run-batch.js +3 -1
  132. package/dist/lib/jobs/run-batch.js.map +1 -1
  133. package/dist/lib/local-api/collection-api.d.ts +42 -1
  134. package/dist/lib/local-api/collection-api.d.ts.map +1 -1
  135. package/dist/lib/local-api/collection-api.js +68 -8
  136. package/dist/lib/local-api/collection-api.js.map +1 -1
  137. package/dist/lib/local-api/index.d.ts +1 -1
  138. package/dist/lib/local-api/index.d.ts.map +1 -1
  139. package/dist/lib/local-api/index.js +1 -1
  140. package/dist/lib/local-api/index.js.map +1 -1
  141. package/dist/lib/plugins/resolver.d.ts.map +1 -1
  142. package/dist/lib/plugins/resolver.js +7 -1
  143. package/dist/lib/plugins/resolver.js.map +1 -1
  144. package/dist/lib/server/api/rate-limit.d.ts +38 -0
  145. package/dist/lib/server/api/rate-limit.d.ts.map +1 -0
  146. package/dist/lib/server/api/rate-limit.js +71 -0
  147. package/dist/lib/server/api/rate-limit.js.map +1 -0
  148. package/dist/lib/server/api/routes/documents-by-id.d.ts.map +1 -1
  149. package/dist/lib/server/api/routes/documents-by-id.js +10 -1
  150. package/dist/lib/server/api/routes/documents-by-id.js.map +1 -1
  151. package/dist/lib/server/api/routes/documents-publish.d.ts.map +1 -1
  152. package/dist/lib/server/api/routes/documents-publish.js +10 -0
  153. package/dist/lib/server/api/routes/documents-publish.js.map +1 -1
  154. package/dist/lib/server/api/routes/documents.d.ts.map +1 -1
  155. package/dist/lib/server/api/routes/documents.js +10 -0
  156. package/dist/lib/server/api/routes/documents.js.map +1 -1
  157. package/dist/lib/server/api/routes/jobs.d.ts.map +1 -1
  158. package/dist/lib/server/api/routes/jobs.js +174 -20
  159. package/dist/lib/server/api/routes/jobs.js.map +1 -1
  160. package/dist/lib/server/api/routes/organizations-by-id.d.ts.map +1 -1
  161. package/dist/lib/server/api/routes/organizations-by-id.js +61 -3
  162. package/dist/lib/server/api/routes/organizations-by-id.js.map +1 -1
  163. package/dist/lib/server/api/routes/organizations-invitations.d.ts.map +1 -1
  164. package/dist/lib/server/api/routes/organizations-invitations.js +31 -2
  165. package/dist/lib/server/api/routes/organizations-invitations.js.map +1 -1
  166. package/dist/lib/server/api/routes/resolve-organization-names.d.ts +17 -0
  167. package/dist/lib/server/api/routes/resolve-organization-names.d.ts.map +1 -0
  168. package/dist/lib/server/api/routes/resolve-organization-names.js +26 -0
  169. package/dist/lib/server/api/routes/resolve-organization-names.js.map +1 -0
  170. package/dist/lib/server/api/routes/user.d.ts +0 -8
  171. package/dist/lib/server/api/routes/user.d.ts.map +1 -1
  172. package/dist/lib/server/api/routes/user.js +144 -0
  173. package/dist/lib/server/api/routes/user.js.map +1 -1
  174. package/dist/lib/server/index.d.ts +6 -1
  175. package/dist/lib/server/index.d.ts.map +1 -1
  176. package/dist/lib/server/index.js +9 -1
  177. package/dist/lib/server/index.js.map +1 -1
  178. package/dist/lib/types/capabilities.d.ts +14 -0
  179. package/dist/lib/types/capabilities.d.ts.map +1 -1
  180. package/dist/lib/types/capabilities.js +37 -18
  181. package/dist/lib/types/capabilities.js.map +1 -1
  182. package/dist/lib/types/events.d.ts +27 -4
  183. package/dist/lib/types/events.d.ts.map +1 -1
  184. package/dist/lib/utils/fetch-remote-file.d.ts.map +1 -1
  185. package/dist/lib/utils/fetch-remote-file.js +32 -3
  186. package/dist/lib/utils/fetch-remote-file.js.map +1 -1
  187. package/dist/local-api/collection-api.d.ts +42 -1
  188. package/dist/local-api/collection-api.d.ts.map +1 -1
  189. package/dist/local-api/collection-api.js +68 -8
  190. package/dist/local-api/index.d.ts +1 -1
  191. package/dist/local-api/index.d.ts.map +1 -1
  192. package/dist/local-api/index.js +1 -1
  193. package/dist/plugins/resolver.d.ts.map +1 -1
  194. package/dist/plugins/resolver.js +7 -1
  195. package/dist/server/api/rate-limit.d.ts +38 -0
  196. package/dist/server/api/rate-limit.d.ts.map +1 -0
  197. package/dist/server/api/rate-limit.js +70 -0
  198. package/dist/server/api/routes/documents-by-id.d.ts.map +1 -1
  199. package/dist/server/api/routes/documents-by-id.js +10 -1
  200. package/dist/server/api/routes/documents-publish.d.ts.map +1 -1
  201. package/dist/server/api/routes/documents-publish.js +10 -0
  202. package/dist/server/api/routes/documents.d.ts.map +1 -1
  203. package/dist/server/api/routes/documents.js +10 -0
  204. package/dist/server/api/routes/jobs.d.ts.map +1 -1
  205. package/dist/server/api/routes/jobs.js +174 -20
  206. package/dist/server/api/routes/organizations-by-id.d.ts.map +1 -1
  207. package/dist/server/api/routes/organizations-by-id.js +61 -3
  208. package/dist/server/api/routes/organizations-invitations.d.ts.map +1 -1
  209. package/dist/server/api/routes/organizations-invitations.js +31 -2
  210. package/dist/server/api/routes/resolve-organization-names.d.ts +17 -0
  211. package/dist/server/api/routes/resolve-organization-names.d.ts.map +1 -0
  212. package/dist/server/api/routes/resolve-organization-names.js +25 -0
  213. package/dist/server/api/routes/user.d.ts +0 -8
  214. package/dist/server/api/routes/user.d.ts.map +1 -1
  215. package/dist/server/api/routes/user.js +144 -0
  216. package/dist/server/index.d.ts +6 -1
  217. package/dist/server/index.d.ts.map +1 -1
  218. package/dist/server/index.js +9 -1
  219. package/dist/types/capabilities.d.ts +14 -0
  220. package/dist/types/capabilities.d.ts.map +1 -1
  221. package/dist/types/capabilities.js +37 -18
  222. package/dist/types/events.d.ts +27 -4
  223. package/dist/types/events.d.ts.map +1 -1
  224. package/dist/utils/fetch-remote-file.d.ts.map +1 -1
  225. package/dist/utils/fetch-remote-file.js +32 -3
  226. package/package.json +6 -6
@@ -0,0 +1,25 @@
1
+ import type { Invitation } from '../types/organization.js';
2
+ /** The minimum shape these predicates need — so callers can pass rows from any layer. */
3
+ export type InvitationState = Pick<Invitation, 'expiresAt' | 'acceptedAt'>;
4
+ /** Default lifetime for a new invitation. */
5
+ export declare const INVITATION_TTL_MS: number;
6
+ /** When a freshly-created invitation should expire. */
7
+ export declare function invitationExpiryFrom(now?: Date): Date;
8
+ export declare function isAccepted(invitation: InvitationState): boolean;
9
+ export declare function isExpired(invitation: InvitationState, now?: Date): boolean;
10
+ /**
11
+ * Redeemable right now: not yet accepted and not yet expired.
12
+ *
13
+ * The only question worth asking in most places — whether a sign-up may proceed,
14
+ * whether to show it in the members list, whether a re-invite is redundant.
15
+ */
16
+ export declare function isPendingInvitation(invitation: InvitationState, now?: Date): boolean;
17
+ /**
18
+ * Lapsed without being used, so it is safe to clear and replace.
19
+ *
20
+ * Deliberately distinct from `!isPendingInvitation(...)`: an *accepted*
21
+ * invitation is also "not pending", but deleting it would erase the record that
22
+ * someone joined by invitation.
23
+ */
24
+ export declare function isStaleInvitation(invitation: InvitationState, now?: Date): boolean;
25
+ //# sourceMappingURL=invitation-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invitation-status.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/invitation-status.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,yFAAyF;AACzF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,YAAY,CAAC,CAAC;AAE3E,6CAA6C;AAC7C,eAAO,MAAM,iBAAiB,QAA0B,CAAC;AAEzD,uDAAuD;AACvD,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,IAAiB,GAAG,IAAI,CAEjE;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAE/D;AAED,wBAAgB,SAAS,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAEtF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAEhG;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAE9F"}
@@ -0,0 +1,42 @@
1
+ // The single definition of what an invitation's state means.
2
+ //
3
+ // This exists because three call sites independently decided what "pending"
4
+ // meant and two of them drifted: the sign-up gate required unaccepted *and*
5
+ // unexpired, the members list did the same, but the re-invite check tested only
6
+ // `acceptedAt === null`. An expired invitation therefore blocked re-inviting
7
+ // while being unusable and invisible — a permanent deadlock for that address.
8
+ //
9
+ // Anything that asks a question about invitation state imports from here. If a
10
+ // rule changes (grace periods, revocation, resend windows), it changes once.
11
+ /** Default lifetime for a new invitation. */
12
+ export const INVITATION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
13
+ /** When a freshly-created invitation should expire. */
14
+ export function invitationExpiryFrom(now = new Date()) {
15
+ return new Date(now.getTime() + INVITATION_TTL_MS);
16
+ }
17
+ export function isAccepted(invitation) {
18
+ return invitation.acceptedAt !== null;
19
+ }
20
+ export function isExpired(invitation, now = new Date()) {
21
+ return new Date(invitation.expiresAt).getTime() <= now.getTime();
22
+ }
23
+ /**
24
+ * Redeemable right now: not yet accepted and not yet expired.
25
+ *
26
+ * The only question worth asking in most places — whether a sign-up may proceed,
27
+ * whether to show it in the members list, whether a re-invite is redundant.
28
+ */
29
+ export function isPendingInvitation(invitation, now = new Date()) {
30
+ return !isAccepted(invitation) && !isExpired(invitation, now);
31
+ }
32
+ /**
33
+ * Lapsed without being used, so it is safe to clear and replace.
34
+ *
35
+ * Deliberately distinct from `!isPendingInvitation(...)`: an *accepted*
36
+ * invitation is also "not pending", but deleting it would erase the record that
37
+ * someone joined by invitation.
38
+ */
39
+ export function isStaleInvitation(invitation, now = new Date()) {
40
+ return !isAccepted(invitation) && isExpired(invitation, now);
41
+ }
42
+ //# sourceMappingURL=invitation-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invitation-status.js","sourceRoot":"","sources":["../../../src/lib/auth/invitation-status.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,gFAAgF;AAChF,6EAA6E;AAC7E,8EAA8E;AAC9E,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAO7E,6CAA6C;AAC7C,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEzD,uDAAuD;AACvD,MAAM,UAAU,oBAAoB,CAAC,MAAY,IAAI,IAAI,EAAE;IAC1D,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,iBAAiB,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,UAA2B;IACrD,OAAO,UAAU,CAAC,UAAU,KAAK,IAAI,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,UAA2B,EAAE,MAAY,IAAI,IAAI,EAAE;IAC5E,OAAO,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAA2B,EAAE,MAAY,IAAI,IAAI,EAAE;IACtF,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAA2B,EAAE,MAAY,IAAI,IAAI,EAAE;IACpF,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,SAAS,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,62 @@
1
+ import type { BootstrapPolicy, InstanceRole } from './bootstrap.js';
2
+ import type { DatabaseAdapter } from '../db/interfaces/index.js';
3
+ import type { UserProfile } from '../types/index.js';
4
+ /**
5
+ * Thrown when sign-up is refused by policy.
6
+ *
7
+ * Providers translate this into whatever their transport expects — Better Auth
8
+ * raises an `APIError`, a REST handler might return 403. The message is written
9
+ * to be safe to show a user.
10
+ */
11
+ export declare class SignUpBlockedError extends Error {
12
+ readonly code = "SIGN_UP_BLOCKED";
13
+ constructor(message: string);
14
+ }
15
+ export interface SignUpGateOptions {
16
+ db: DatabaseAdapter;
17
+ /** Address attempting to register. Matched case-insensitively. */
18
+ email: string | undefined;
19
+ /** When false the gate is disabled entirely and this is a no-op. */
20
+ inviteOnly: boolean;
21
+ }
22
+ /**
23
+ * Throws `SignUpBlockedError` unless this address may create an account.
24
+ *
25
+ * Two ways through: the instance is provably empty (nobody exists yet to have
26
+ * sent an invitation, so gating the first sign-up would lock the door with the
27
+ * keys inside), or the address holds a pending invitation.
28
+ *
29
+ * Narrowing that first exception is the bootstrap policy's job — see
30
+ * `resolveBootstrapRole` — not this gate's.
31
+ */
32
+ export declare function assertSignUpAllowed({ db, email, inviteOnly }: SignUpGateOptions): Promise<void>;
33
+ export interface BootstrapRoleOptions {
34
+ db: DatabaseAdapter;
35
+ user: {
36
+ id: string;
37
+ email: string;
38
+ emailVerified: boolean;
39
+ };
40
+ /** The request that triggered profile creation, when there is one. */
41
+ request?: Request;
42
+ bootstrap: BootstrapPolicy;
43
+ }
44
+ /**
45
+ * Runs the bootstrap policy for a brand-new user, returning the instance role to
46
+ * grant or `null` for none.
47
+ *
48
+ * Deliberately not wrapped in a transaction. SQLite allows a single writer, and
49
+ * holding that lock across the policy's own round-trips collides with the auth
50
+ * provider's concurrent user/session inserts (SQLITE_BUSY). The atomicity was
51
+ * only partial anyway — see `claimCode`: a claim is single-use, not mutually
52
+ * exclusive.
53
+ */
54
+ export declare function resolveBootstrapRole({ db, user, request, bootstrap }: BootstrapRoleOptions): Promise<InstanceRole | null>;
55
+ /**
56
+ * The one place a CMS user profile is created for a newly-authenticated user.
57
+ *
58
+ * Every `AuthProvider` should route new profiles through here so bootstrap
59
+ * promotion happens identically regardless of who did the authenticating.
60
+ */
61
+ export declare function createUserProfileWithBootstrap(options: BootstrapRoleOptions): Promise<UserProfile>;
62
+ //# sourceMappingURL=sign-up-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign-up-policy.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/sign-up-policy.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,qBAAqB;gBACtB,OAAO,EAAE,MAAM;CAI3B;AAED,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,eAAe,CAAC;IACpB,kEAAkE;IAClE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,oEAAoE;IACpE,UAAU,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,EACzC,EAAE,EACF,KAAK,EACL,UAAU,EACV,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBnC;AAED,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,eAAe,CAAC;IACpB,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5D,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,eAAe,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,EAC1C,EAAE,EACF,IAAI,EACJ,OAAO,EACP,SAAS,EACT,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAerD;AAuBD;;;;;GAKG;AACH,wBAAsB,8BAA8B,CACnD,OAAO,EAAE,oBAAoB,GAC3B,OAAO,CAAC,WAAW,CAAC,CA6BtB"}
@@ -0,0 +1,121 @@
1
+ // Who may sign up, and what they get when they do.
2
+ //
3
+ // These are **CMS policy decisions, not auth-provider concerns**. Whether an
4
+ // instance is invite-only, and who becomes its first administrator, should mean
5
+ // the same thing whether the app authenticates with Better Auth, Keycloak,
6
+ // Supabase, or something hand-rolled — so they live behind the `AuthProvider`
7
+ // port rather than inside any one implementation of it.
8
+ //
9
+ // Before this existed, both rules were enforced inside `@aphexcms/auth`'s
10
+ // Better Auth wiring, which meant a third-party provider silently inherited
11
+ // neither: `inviteOnly: true` would be configured and quietly do nothing. An
12
+ // implementation is free to call these wherever its lifecycle allows, but it is
13
+ // no longer free to *re-derive* them.
14
+ import { cmsLogger } from '../utils/logger.js';
15
+ import { isInstanceEmpty, canDetermineInstanceEmptiness } from './instance-state.js';
16
+ import { isPendingInvitation } from './invitation-status.js';
17
+ /**
18
+ * Thrown when sign-up is refused by policy.
19
+ *
20
+ * Providers translate this into whatever their transport expects — Better Auth
21
+ * raises an `APIError`, a REST handler might return 403. The message is written
22
+ * to be safe to show a user.
23
+ */
24
+ export class SignUpBlockedError extends Error {
25
+ code = 'SIGN_UP_BLOCKED';
26
+ constructor(message) {
27
+ super(message);
28
+ this.name = 'SignUpBlockedError';
29
+ }
30
+ }
31
+ /**
32
+ * Throws `SignUpBlockedError` unless this address may create an account.
33
+ *
34
+ * Two ways through: the instance is provably empty (nobody exists yet to have
35
+ * sent an invitation, so gating the first sign-up would lock the door with the
36
+ * keys inside), or the address holds a pending invitation.
37
+ *
38
+ * Narrowing that first exception is the bootstrap policy's job — see
39
+ * `resolveBootstrapRole` — not this gate's.
40
+ */
41
+ export async function assertSignUpAllowed({ db, email, inviteOnly }) {
42
+ if (!inviteOnly)
43
+ return;
44
+ if (await isInstanceEmpty(db)) {
45
+ cmsLogger.info('[SignUp]', 'Allowing first sign-up on an empty instance');
46
+ return;
47
+ }
48
+ // Invitations are stored lower-cased and matched exactly, so a mixed-case
49
+ // sign-up would otherwise find nothing and be refused.
50
+ const address = email?.toLowerCase().trim();
51
+ const invitations = address ? await db.findInvitationsByEmail(address) : [];
52
+ if (!invitations.some((invitation) => isPendingInvitation(invitation))) {
53
+ cmsLogger.warn('[SignUp]', `Blocked sign-up for ${address} — no pending invitation`);
54
+ throw new SignUpBlockedError('Sign-up is by invitation only. Ask an administrator for an invite.');
55
+ }
56
+ }
57
+ /**
58
+ * Runs the bootstrap policy for a brand-new user, returning the instance role to
59
+ * grant or `null` for none.
60
+ *
61
+ * Deliberately not wrapped in a transaction. SQLite allows a single writer, and
62
+ * holding that lock across the policy's own round-trips collides with the auth
63
+ * provider's concurrent user/session inserts (SQLITE_BUSY). The atomicity was
64
+ * only partial anyway — see `claimCode`: a claim is single-use, not mutually
65
+ * exclusive.
66
+ */
67
+ export async function resolveBootstrapRole({ db, user, request, bootstrap }) {
68
+ // "Provably empty", not "couldn't prove otherwise". An adapter that can't
69
+ // answer this must not read as "no users exist" — that inversion once
70
+ // promoted every single sign-up to super admin.
71
+ const isFirstUser = await isInstanceEmpty(db);
72
+ if (!canDetermineInstanceEmptiness(db)) {
73
+ cmsLogger.warn('[SignUp]', 'Database adapter does not implement hasAnyUserProfiles() — skipping bootstrap. ' +
74
+ 'Provision the first administrator out of band.');
75
+ }
76
+ return bootstrap({ user, isFirstUser, request, db });
77
+ }
78
+ /**
79
+ * Take the one-time bootstrap claim, or report that somebody else has it.
80
+ *
81
+ * An adapter without `tryClaimBootstrap` keeps the old, racy behaviour rather
82
+ * than never promoting anyone — the opposite of the rule `isInstanceEmpty`
83
+ * follows, and on purpose. There, failing closed skips a promotion; here it
84
+ * would leave a fresh install with no administrator and no way to get one.
85
+ */
86
+ async function claimBootstrapPromotion(db) {
87
+ const claim = db.tryClaimBootstrap?.bind(db);
88
+ if (!claim) {
89
+ cmsLogger.warn('[SignUp]', 'Database adapter does not implement tryClaimBootstrap() — bootstrap promotion is not ' +
90
+ 'race-safe. Concurrent first sign-ups could both be promoted.');
91
+ return true;
92
+ }
93
+ return claim();
94
+ }
95
+ /**
96
+ * The one place a CMS user profile is created for a newly-authenticated user.
97
+ *
98
+ * Every `AuthProvider` should route new profiles through here so bootstrap
99
+ * promotion happens identically regardless of who did the authenticating.
100
+ */
101
+ export async function createUserProfileWithBootstrap(options) {
102
+ const proposed = await resolveBootstrapRole(options);
103
+ // The policy decided *whether* this user qualifies; the claim decides whether
104
+ // they got there first. Both are needed: the policy's own `isFirstUser` check
105
+ // reads a snapshot taken before the profile insert, so two sign-ups racing on
106
+ // a fresh instance can both pass it and both be promoted. Every promotion in
107
+ // the instance's lifetime funnels through this one call, so claiming here
108
+ // covers every policy — including a custom one — without each having to know
109
+ // about the race.
110
+ const granted = proposed && (await claimBootstrapPromotion(options.db)) ? proposed : null;
111
+ if (proposed && !granted) {
112
+ cmsLogger.warn('[SignUp]', `Bootstrap promotion for ${options.user.id} lost the claim — another sign-up got there first. ` +
113
+ 'Creating an ordinary profile instead.');
114
+ }
115
+ cmsLogger.info('[SignUp]', `Creating profile for ${options.user.id}${granted ? ` with role ${granted.toUpperCase()}` : ''}`);
116
+ return options.db.createUserProfile({
117
+ userId: options.user.id,
118
+ role: granted ?? 'editor'
119
+ });
120
+ }
121
+ //# sourceMappingURL=sign-up-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign-up-policy.js","sourceRoot":"","sources":["../../../src/lib/auth/sign-up-policy.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,EAAE;AACF,6EAA6E;AAC7E,gFAAgF;AAChF,2EAA2E;AAC3E,8EAA8E;AAC9E,wDAAwD;AACxD,EAAE;AACF,0EAA0E;AAC1E,4EAA4E;AAC5E,6EAA6E;AAC7E,gFAAgF;AAChF,sCAAsC;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAK1D;;;;;;GAMG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IACnC,IAAI,GAAG,iBAAiB,CAAC;IAClC,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IAClC,CAAC;CACD;AAUD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,EACzC,EAAE,EACF,KAAK,EACL,UAAU,EACS;IACnB,IAAI,CAAC,UAAU;QAAE,OAAO;IAExB,IAAI,MAAM,eAAe,CAAC,EAAE,CAAC,EAAE,CAAC;QAC/B,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,6CAA6C,CAAC,CAAC;QAC1E,OAAO;IACR,CAAC;IAED,0EAA0E;IAC1E,uDAAuD;IACvD,MAAM,OAAO,GAAG,KAAK,EAAE,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAC5C,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAE5E,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;QACxE,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,uBAAuB,OAAO,0BAA0B,CAAC,CAAC;QACrF,MAAM,IAAI,kBAAkB,CAC3B,oEAAoE,CACpE,CAAC;IACH,CAAC;AACF,CAAC;AAUD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,EAC1C,EAAE,EACF,IAAI,EACJ,OAAO,EACP,SAAS,EACa;IACtB,0EAA0E;IAC1E,sEAAsE;IACtE,gDAAgD;IAChD,MAAM,WAAW,GAAG,MAAM,eAAe,CAAC,EAAE,CAAC,CAAC;IAE9C,IAAI,CAAC,6BAA6B,CAAC,EAAE,CAAC,EAAE,CAAC;QACxC,SAAS,CAAC,IAAI,CACb,UAAU,EACV,iFAAiF;YAChF,gDAAgD,CACjD,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,uBAAuB,CAAC,EAAmB;IACzD,MAAM,KAAK,GAAG,EAAE,CAAC,iBAAiB,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7C,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,SAAS,CAAC,IAAI,CACb,UAAU,EACV,uFAAuF;YACtF,8DAA8D,CAC/D,CAAC;QACF,OAAO,IAAI,CAAC;IACb,CAAC;IACD,OAAO,KAAK,EAAE,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CACnD,OAA6B;IAE7B,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAErD,8EAA8E;IAC9E,8EAA8E;IAC9E,8EAA8E;IAC9E,6EAA6E;IAC7E,0EAA0E;IAC1E,6EAA6E;IAC7E,kBAAkB;IAClB,MAAM,OAAO,GAAG,QAAQ,IAAI,CAAC,MAAM,uBAAuB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IAE1F,IAAI,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;QAC1B,SAAS,CAAC,IAAI,CACb,UAAU,EACV,2BAA2B,OAAO,CAAC,IAAI,CAAC,EAAE,qDAAqD;YAC9F,uCAAuC,CACxC,CAAC;IACH,CAAC;IAED,SAAS,CAAC,IAAI,CACb,UAAU,EACV,wBAAwB,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,cAAc,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAChG,CAAC;IAEF,OAAO,OAAO,CAAC,EAAE,CAAC,iBAAiB,CAAC;QACnC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE;QACvB,IAAI,EAAE,OAAO,IAAI,QAAQ;KACzB,CAAC,CAAC;AACJ,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { DomainEvent, AppendEventInput, Job, ScheduleJobInput, ClaimJobsOptions, ListEventsOptions, ListJobsOptions, OutboxRow, ListUnprocessedOutboxOptions, Page } from '../../types/events.js';
1
+ import type { DomainEvent, AppendEventInput, Job, ScheduleJobInput, ClaimJobsOptions, ListEventsOptions, ListJobsOptions, OutboxRow, ListUnprocessedOutboxOptions, OutboxHealth, Page } from '../../types/events.js';
2
2
  /**
3
3
  * The durable spine: an append-only domain-event log plus a DB-backed job queue.
4
4
  * All rows are organization-scoped. (The event-log read API — `listEvents` — lands
@@ -25,6 +25,13 @@ export interface EventJobAdapter {
25
25
  listUnprocessedOutbox(options: ListUnprocessedOutboxOptions): Promise<OutboxRow[]>;
26
26
  /** Stamp `processedAt` once an outbox row has been fanned out to every subscriber. */
27
27
  markOutboxProcessed(organizationId: string, id: string): Promise<void>;
28
+ /**
29
+ * Size and age of the unprocessed backlog — the "is the relay running?" signal.
30
+ * Omit `organizationId` for the instance-wide figure. See {@link OutboxHealth}.
31
+ */
32
+ outboxHealth(options: {
33
+ organizationId?: string;
34
+ }): Promise<OutboxHealth>;
28
35
  /** Schedule a job. Idempotent when `idempotencyKey` is set: a duplicate key returns the existing job. */
29
36
  scheduleJob(input: ScheduleJobInput): Promise<Job>;
30
37
  /**
@@ -49,7 +56,27 @@ export interface EventJobAdapter {
49
56
  }): Promise<void>;
50
57
  /** Cancel a job (e.g. a scheduled publish the user called off): status → `cancelled`, clear the lease. */
51
58
  cancelJob(organizationId: string, id: string): Promise<void>;
52
- /** List jobs for the org, newest first (read-only history / observability). */
59
+ /** Read a single job by id (org-scoped). Null when it doesn't exist in that org. */
60
+ getJob(organizationId: string, id: string): Promise<Job | null>;
61
+ /**
62
+ * Put a finished job back on the queue by hand — the operator's undo for a dead letter.
63
+ *
64
+ * Distinct from `retryJob`, which is the *runner's* backoff transition and deliberately
65
+ * leaves `attempts` alone. A dead-lettered job sits at `attempts === maxAttempts`, so
66
+ * reusing `retryJob` would hand it back to the runner only for the next claim to exhaust
67
+ * it again immediately. This resets the attempt counter and clears `lastError`, giving
68
+ * the job a fresh budget once whatever broke has been fixed.
69
+ *
70
+ * Guarded to `failed` and `cancelled` in SQL: requeueing a `pending` job is pointless and
71
+ * requeueing a `leased` one would race the worker currently holding it, whose settle would
72
+ * then overwrite the status. Returns the updated job, or null when the id doesn't exist in
73
+ * the org or wasn't in a requeueable state — so callers can tell "gone" from "not allowed"
74
+ * with a follow-up `getJob`.
75
+ */
76
+ requeueJob(organizationId: string, id: string, options: {
77
+ runAt: Date;
78
+ }): Promise<Job | null>;
79
+ /** List jobs, newest first (read-only history / observability). Omit `organizationId` for instance-wide. */
53
80
  listJobs(options: ListJobsOptions): Promise<Page<Job>>;
54
81
  }
55
82
  //# sourceMappingURL=events.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../../../src/lib/db/interfaces/events.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,WAAW,EACX,gBAAgB,EAChB,GAAG,EACH,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,SAAS,EACT,4BAA4B,EAC5B,IAAI,EACJ,MAAM,oBAAoB,CAAC;AAE5B;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAE/B;;;;;;OAMG;IACH,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3D,8CAA8C;IAC9C,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC1E,wFAAwF;IACxF,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IAGnE;;;;OAIG;IACH,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACnF,sFAAsF;IACtF,mBAAmB,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAGvE,yGAAyG;IACzG,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACnD;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACxD,oCAAoC;IACpC,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CACP,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACrC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,uFAAuF;IACvF,OAAO,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvF,0GAA0G;IAC1G,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;CACvD"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../../../src/lib/db/interfaces/events.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,WAAW,EACX,gBAAgB,EAChB,GAAG,EACH,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,SAAS,EACT,4BAA4B,EAC5B,YAAY,EACZ,IAAI,EACJ,MAAM,oBAAoB,CAAC;AAE5B;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAE/B;;;;;;OAMG;IACH,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3D,8CAA8C;IAC9C,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC1E,wFAAwF;IACxF,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IAGnE;;;;OAIG;IACH,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACnF,sFAAsF;IACtF,mBAAmB,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAG1E,yGAAyG;IACzG,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACnD;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACxD,oCAAoC;IACpC,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CACP,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACrC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,uFAAuF;IACvF,OAAO,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvF,0GAA0G;IAC1G,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,oFAAoF;IACpF,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;IAChE;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,KAAK,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;IAC9F,4GAA4G;IAC5G,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;CACvD"}
@@ -17,6 +17,7 @@ export type { UserProfileAdapter, NewUserProfileData } from './user.js';
17
17
  export type { SchemaAdapter } from './schema.js';
18
18
  export type { OrganizationAdapter } from './organization.js';
19
19
  export type { InstanceAdapter, InstanceSettings } from './instance.js';
20
+ export { BOOTSTRAP_CLAIM_ID } from './instance.js';
20
21
  export type { RolesAdapter } from './role.js';
21
22
  export type { ReferenceAdapter, BackReferenceRow, BackReferenceLookup } from './reference.js';
22
23
  export type { PluginSettingsAdapter, PluginSettingsRow } from './plugin-settings.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/db/interfaces/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/db/interfaces/index.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,3 @@
1
1
  export { RevisionConflictError } from './document.js';
2
+ export { BOOTSTRAP_CLAIM_ID } from './instance.js';
2
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/db/interfaces/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/db/interfaces/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAMnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
@@ -1,7 +1,36 @@
1
1
  import type { InstanceSettings } from '../../types/instance.js';
2
2
  export type { InstanceSettings };
3
+ /**
4
+ * Row id holding the one-time bootstrap claim.
5
+ *
6
+ * Deliberately not `'default'` — `get`/`updateInstanceSettings` are scoped to
7
+ * that row, so this one is invisible to them and can't be cleared by an ordinary
8
+ * settings write.
9
+ */
10
+ export declare const BOOTSTRAP_CLAIM_ID = "aphex:bootstrap-claim";
3
11
  export interface InstanceAdapter {
4
12
  getInstanceSettings(): Promise<InstanceSettings>;
5
13
  updateInstanceSettings(settings: Partial<InstanceSettings>): Promise<InstanceSettings>;
14
+ /**
15
+ * Atomically claim the one-time bootstrap promotion. `true` means this caller
16
+ * won and may grant an instance role; `false` means somebody else already did.
17
+ *
18
+ * Exists because "is the instance empty?" and "insert the first profile" were
19
+ * two statements with a gap between them: two sign-ups arriving together could
20
+ * both observe an empty instance and both be promoted to super admin. Every
21
+ * bootstrap policy gates on `isFirstUser`, so at most one promotion is
22
+ * intended in all of them — this makes that atomic rather than likely.
23
+ *
24
+ * Implemented as an insert of a dedicated row that conflicts with itself, so
25
+ * the database decides the winner in a single statement. That matters more
26
+ * than it sounds: the obvious fix — wrapping the check and the insert in one
27
+ * transaction — holds SQLite's single write lock across the auth provider's
28
+ * own concurrent user and session inserts and fails sign-up with SQLITE_BUSY.
29
+ *
30
+ * Optional: an adapter that doesn't implement it keeps the old non-atomic
31
+ * behaviour (with a warning) rather than refusing to ever promote anyone,
32
+ * which would leave a fresh install with no way to get its first administrator.
33
+ */
34
+ tryClaimBootstrap?(): Promise<boolean>;
6
35
  }
7
36
  //# sourceMappingURL=instance.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../../../src/lib/db/interfaces/instance.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,MAAM,WAAW,eAAe;IAC/B,mBAAmB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACjD,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACvF"}
1
+ {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../../../src/lib/db/interfaces/instance.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC/B,mBAAmB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACjD,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEvF;;;;;;;;;;;;;;;;;;;OAmBG;IACH,iBAAiB,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACvC"}
@@ -1,2 +1,9 @@
1
- export {};
1
+ /**
2
+ * Row id holding the one-time bootstrap claim.
3
+ *
4
+ * Deliberately not `'default'` — `get`/`updateInstanceSettings` are scoped to
5
+ * that row, so this one is invisible to them and can't be cleared by an ordinary
6
+ * settings write.
7
+ */
8
+ export const BOOTSTRAP_CLAIM_ID = 'aphex:bootstrap-claim';
2
9
  //# sourceMappingURL=instance.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"instance.js","sourceRoot":"","sources":["../../../../src/lib/db/interfaces/instance.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"instance.js","sourceRoot":"","sources":["../../../../src/lib/db/interfaces/instance.ts"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { EventConsumerPart } from '../plugins/types.js';
2
+ /**
3
+ * Erase a deleted user's avatar — the file, not just the row.
4
+ *
5
+ * Account deletion removes the user record, and with it the only pointer to the avatar
6
+ * asset. Without this the image survives in object storage indefinitely, reachable by
7
+ * anyone holding its URL and invisible to the media browser (avatars are marked
8
+ * `system`), so nothing would ever surface it for cleanup. That is precisely the
9
+ * "personal data with no way left to find or erase it" case a right-to-erasure request
10
+ * has to be able to answer.
11
+ *
12
+ * Idempotent, as every consumer must be: a missing asset is a completed erasure, so a
13
+ * redelivery finds nothing and succeeds. It throws only when the delete itself fails, so
14
+ * a transient storage outage retries with backoff instead of silently giving up — the
15
+ * one outcome that would leave the data behind while reporting success.
16
+ */
17
+ export declare const eraseUserAvatarConsumer: EventConsumerPart;
18
+ /** Every consumer core ships. Seeded into the part resolver ahead of plugin parts. */
19
+ export declare const BUILT_IN_EVENT_CONSUMERS: readonly EventConsumerPart[];
20
+ //# sourceMappingURL=built-in-consumers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"built-in-consumers.d.ts","sourceRoot":"","sources":["../../../src/lib/events/built-in-consumers.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAQ1D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uBAAuB,EAAE,iBAoCrC,CAAC;AAEF,sFAAsF;AACtF,eAAO,MAAM,wBAAwB,EAAE,SAAS,iBAAiB,EAA8B,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { userDeleted } from './catalog.js';
2
+ /** Extract the asset id from an avatar path, or null if it isn't one of ours. */
3
+ function avatarAssetId(image) {
4
+ return /^\/media\/([^/]+)\//.exec(image)?.[1] ?? null;
5
+ }
6
+ /**
7
+ * Erase a deleted user's avatar — the file, not just the row.
8
+ *
9
+ * Account deletion removes the user record, and with it the only pointer to the avatar
10
+ * asset. Without this the image survives in object storage indefinitely, reachable by
11
+ * anyone holding its URL and invisible to the media browser (avatars are marked
12
+ * `system`), so nothing would ever surface it for cleanup. That is precisely the
13
+ * "personal data with no way left to find or erase it" case a right-to-erasure request
14
+ * has to be able to answer.
15
+ *
16
+ * Idempotent, as every consumer must be: a missing asset is a completed erasure, so a
17
+ * redelivery finds nothing and succeeds. It throws only when the delete itself fails, so
18
+ * a transient storage outage retries with backoff instead of silently giving up — the
19
+ * one outcome that would leave the data behind while reporting success.
20
+ */
21
+ export const eraseUserAvatarConsumer = {
22
+ implements: 'aphex/event/consumer',
23
+ id: 'aphex.erase-user-avatar',
24
+ events: [userDeleted.type],
25
+ async handler({ event, assetService, logger }) {
26
+ const image = event.payload.image;
27
+ if (typeof image !== 'string' || !image)
28
+ return;
29
+ const assetId = avatarAssetId(image);
30
+ if (!assetId) {
31
+ // An external provider's avatar, or one of the absolute storage URLs written
32
+ // before avatars moved to `/media/<id>/`. Nothing of ours to delete, and no
33
+ // asset id recoverable from it — flag it so it can be cleared by hand.
34
+ logger.warn('[erase-user-avatar]', `User ${event.payload.userId} had a non-asset avatar (${image}); nothing to erase automatically.`);
35
+ return;
36
+ }
37
+ if (!assetService) {
38
+ // Throwing would retry forever against a host that has no asset service at
39
+ // all. Say so loudly once per attempt and let the delivery dead-letter.
40
+ throw new Error(`Cannot erase avatar asset ${assetId}: no asset service is configured on this host.`);
41
+ }
42
+ const deleted = await assetService.deleteAsset(event.organizationId, assetId);
43
+ logger.info('[erase-user-avatar]', deleted
44
+ ? `Erased avatar asset ${assetId} for deleted user ${event.payload.userId}`
45
+ : `Avatar asset ${assetId} was already gone for user ${event.payload.userId}`);
46
+ }
47
+ };
48
+ /** Every consumer core ships. Seeded into the part resolver ahead of plugin parts. */
49
+ export const BUILT_IN_EVENT_CONSUMERS = [eraseUserAvatarConsumer];
50
+ //# sourceMappingURL=built-in-consumers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"built-in-consumers.js","sourceRoot":"","sources":["../../../src/lib/events/built-in-consumers.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,iFAAiF;AACjF,SAAS,aAAa,CAAC,KAAa;IACnC,OAAO,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAsB;IACzD,UAAU,EAAE,sBAAsB;IAClC,EAAE,EAAE,yBAAyB;IAC7B,MAAM,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC;IAC1B,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE;QAC5C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAClC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK;YAAE,OAAO;QAEhD,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,6EAA6E;YAC7E,4EAA4E;YAC5E,uEAAuE;YACvE,MAAM,CAAC,IAAI,CACV,qBAAqB,EACrB,QAAQ,KAAK,CAAC,OAAO,CAAC,MAAM,4BAA4B,KAAK,oCAAoC,CACjG,CAAC;YACF,OAAO;QACR,CAAC;QAED,IAAI,CAAC,YAAY,EAAE,CAAC;YACnB,2EAA2E;YAC3E,wEAAwE;YACxE,MAAM,IAAI,KAAK,CACd,6BAA6B,OAAO,gDAAgD,CACpF,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,WAAW,CAAC,KAAK,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;QAC9E,MAAM,CAAC,IAAI,CACV,qBAAqB,EACrB,OAAO;YACN,CAAC,CAAC,uBAAuB,OAAO,qBAAqB,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE;YAC3E,CAAC,CAAC,gBAAgB,OAAO,8BAA8B,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAC9E,CAAC;IACH,CAAC;CACD,CAAC;AAEF,sFAAsF;AACtF,MAAM,CAAC,MAAM,wBAAwB,GAAiC,CAAC,uBAAuB,CAAC,CAAC"}
@@ -5,4 +5,20 @@ export declare const documentPublished: import("./define-event.js").EventDefinit
5
5
  documentType: z.ZodString;
6
6
  publishedHash: z.ZodNullable<z.ZodString>;
7
7
  }, z.core.$strip>>;
8
+ /**
9
+ * Emitted when a user account is deleted, once per organization they belonged to — the
10
+ * erasure fan-out point. Consumers react by removing whatever that user left behind in
11
+ * *their* organization, so "delete my account" reaches per-org data without the deletion
12
+ * path having to know every consumer that cares.
13
+ *
14
+ * Carries `image` because it can't be looked up afterwards: the account row is gone by the
15
+ * time a consumer runs, taking the only pointer to the avatar asset with it. This is the
16
+ * one case where the payload holds a value rather than an identifier, and it's still not a
17
+ * secret — it's the same public CDN path the profile served.
18
+ */
19
+ export declare const userDeleted: import("./define-event.js").EventDefinition<"user.deleted", z.ZodObject<{
20
+ userId: z.ZodString;
21
+ email: z.ZodNullable<z.ZodString>;
22
+ image: z.ZodNullable<z.ZodString>;
23
+ }, z.core.$strip>>;
8
24
  //# sourceMappingURL=catalog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/lib/events/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,+FAA+F;AAC/F,eAAO,MAAM,iBAAiB;;;;kBAO7B,CAAC"}
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/lib/events/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,+FAA+F;AAC/F,eAAO,MAAM,iBAAiB;;;;kBAO7B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW;;;;kBAQvB,CAAC"}
@@ -9,4 +9,21 @@ export const documentPublished = defineEvent('document.published', z.object({
9
9
  documentType: z.string(),
10
10
  publishedHash: z.string().nullable()
11
11
  }));
12
+ /**
13
+ * Emitted when a user account is deleted, once per organization they belonged to — the
14
+ * erasure fan-out point. Consumers react by removing whatever that user left behind in
15
+ * *their* organization, so "delete my account" reaches per-org data without the deletion
16
+ * path having to know every consumer that cares.
17
+ *
18
+ * Carries `image` because it can't be looked up afterwards: the account row is gone by the
19
+ * time a consumer runs, taking the only pointer to the avatar asset with it. This is the
20
+ * one case where the payload holds a value rather than an identifier, and it's still not a
21
+ * secret — it's the same public CDN path the profile served.
22
+ */
23
+ export const userDeleted = defineEvent('user.deleted', z.object({
24
+ userId: z.string(),
25
+ email: z.string().nullable(),
26
+ /** The profile image as stored, or null. `/media/<assetId>/<filename>` when it's ours. */
27
+ image: z.string().nullable()
28
+ }));
12
29
  //# sourceMappingURL=catalog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../../src/lib/events/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,wFAAwF;AACxF,4FAA4F;AAC5F,kFAAkF;AAElF,+FAA+F;AAC/F,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAC3C,oBAAoB,EACpB,CAAC,CAAC,MAAM,CAAC;IACR,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACpC,CAAC,CACF,CAAC"}
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../../src/lib/events/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,wFAAwF;AACxF,4FAA4F;AAC5F,kFAAkF;AAElF,+FAA+F;AAC/F,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAC3C,oBAAoB,EACpB,CAAC,CAAC,MAAM,CAAC;IACR,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACpC,CAAC,CACF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,WAAW,CACrC,cAAc,EACd,CAAC,CAAC,MAAM,CAAC;IACR,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,0FAA0F;IAC1F,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CACF,CAAC"}
@@ -32,11 +32,23 @@ export interface ConsumerSettingsReader {
32
32
  /** Decrypted settings for `pluginId` in the event's organization ({} if never configured). */
33
33
  get(pluginId: string): Promise<Record<string, unknown>>;
34
34
  }
35
+ /**
36
+ * The asset operations a consumer may perform. Deliberately the *service*, not the database
37
+ * adapter: `databaseAdapter.deleteAsset` drops the row and leaves the file in object storage,
38
+ * which for an erasure consumer is the difference between deleting the pointer and deleting
39
+ * the data.
40
+ */
41
+ export interface ConsumerAssetService {
42
+ findAssetById(organizationId: string, id: string): Promise<unknown>;
43
+ deleteAsset(organizationId: string, id: string): Promise<boolean>;
44
+ }
35
45
  /** What an event-consumer handler receives. `databaseAdapter` is the live adapter (org-scoped calls take an org id). */
36
46
  export interface EventConsumerContext {
37
47
  event: ConsumedEvent;
38
48
  databaseAdapter: DatabaseAdapter;
39
49
  logger: Logger;
50
+ /** Assets in the event's organization, file included. `null` when the host wired none. */
51
+ assetService: ConsumerAssetService | null;
40
52
  /** Read this plugin's own decrypted settings for the event's org (e.g. a webhook URL secret). */
41
53
  settings: ConsumerSettingsReader;
42
54
  /**
@@ -49,6 +61,8 @@ export interface EventConsumerContext {
49
61
  /** Runtime services the consumer wrapper injects into each delivery's context. */
50
62
  export interface ConsumerHandlerDeps {
51
63
  pluginSettingsService: PluginSettingsReader;
64
+ /** The app's asset service, passed through to consumers that erase or rewrite media. */
65
+ assetService?: ConsumerAssetService | null;
52
66
  /** The app's email adapter (or `null` if email isn't configured). Passed straight to the consumer. */
53
67
  emailAdapter?: EmailAdapter | null;
54
68
  }
@@ -1 +1 @@
1
- {"version":3,"file":"consumer.d.ts","sourceRoot":"","sources":["../../../src/lib/events/consumer.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,IAAI,CAAC;CAChB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACpC,GAAG,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAChF;AAED,iFAAiF;AACjF,MAAM,WAAW,sBAAsB;IACtC,8FAA8F;IAC9F,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACxD;AAED,wHAAwH;AACxH,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,aAAa,CAAC;IACrB,eAAe,EAAE,eAAe,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,iGAAiG;IACjG,QAAQ,EAAE,sBAAsB,CAAC;IACjC;;;;OAIG;IACH,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;CAClC;AAED,kFAAkF;AAClF,MAAM,WAAW,mBAAmB;IACnC,qBAAqB,EAAE,oBAAoB,CAAC;IAC5C,sGAAsG;IACtG,YAAY,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;CACnC;AAED;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,+CAA+C;AAC/C,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1D;AAqBD,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAa/E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,mBAAmB,GACvB,UAAU,CAcZ"}
1
+ {"version":3,"file":"consumer.d.ts","sourceRoot":"","sources":["../../../src/lib/events/consumer.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,IAAI,CAAC;CAChB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACpC,GAAG,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAChF;AAED,iFAAiF;AACjF,MAAM,WAAW,sBAAsB;IACtC,8FAA8F;IAC9F,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACxD;AAED;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACpC,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpE,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAClE;AAED,wHAAwH;AACxH,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,aAAa,CAAC;IACrB,eAAe,EAAE,eAAe,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC1C,iGAAiG;IACjG,QAAQ,EAAE,sBAAsB,CAAC;IACjC;;;;OAIG;IACH,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;CAClC;AAED,kFAAkF;AAClF,MAAM,WAAW,mBAAmB;IACnC,qBAAqB,EAAE,oBAAoB,CAAC;IAC5C,wFAAwF;IACxF,YAAY,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC3C,sGAAsG;IACtG,YAAY,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;CACnC;AAED;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,+CAA+C;AAC/C,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1D;AAqBD,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAa/E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,mBAAmB,GACvB,UAAU,CAeZ"}
@@ -70,6 +70,7 @@ export function toConsumerJobHandler(handler, deps) {
70
70
  databaseAdapter,
71
71
  logger,
72
72
  settings,
73
+ assetService: deps.assetService ?? null,
73
74
  emailAdapter: deps.emailAdapter ?? null
74
75
  });
75
76
  };