@cosmicdrift/kumiko-bundled-features 0.339.0 → 0.341.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 (166) hide show
  1. package/dist/auth-email-password/bootstrap.d.ts +74 -0
  2. package/dist/auth-email-password/bootstrap.js +165 -0
  3. package/dist/auth-email-password/constants.d.ts +2 -0
  4. package/dist/auth-email-password/constants.js +5 -0
  5. package/dist/auth-email-password/errors.d.ts +1 -0
  6. package/dist/auth-email-password/errors.js +6 -0
  7. package/dist/auth-email-password/feature.js +2 -1
  8. package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +9 -1
  9. package/dist/auth-email-password/handlers/invite-accept.write.js +8 -0
  10. package/dist/auth-email-password/handlers/invite-create.write.d.ts +11 -0
  11. package/dist/auth-email-password/handlers/invite-create.write.js +134 -87
  12. package/dist/auth-email-password/handlers/invite-signup-complete.write.js +5 -1
  13. package/dist/auth-email-password/handlers/signup-request.write.js +1 -0
  14. package/dist/auth-email-password/handlers/token-request-handler.js +1 -0
  15. package/dist/auth-email-password/index.d.ts +2 -0
  16. package/dist/auth-email-password/index.js +1 -0
  17. package/dist/auth-email-password/invite-token-store.d.ts +3 -0
  18. package/dist/auth-email-password/invite-token-store.js +5 -0
  19. package/dist/auth-email-password/magic-link-mail.js +1 -0
  20. package/dist/auth-mfa/config.d.ts +1 -1
  21. package/dist/auth-mfa/config.js +8 -3
  22. package/dist/auth-mfa/feature.d.ts +2 -0
  23. package/dist/auth-mfa/feature.js +2 -1
  24. package/dist/auth-mfa/mfa-status-checker.d.ts +2 -0
  25. package/dist/auth-mfa/mfa-status-checker.js +1 -1
  26. package/dist/billing-foundation/consumer-protection/issue-confirmation.js +1 -0
  27. package/dist/billing-foundation/consumer-protection/termination-mail.js +1 -0
  28. package/dist/billing-foundation/consumer-protection/termination-notify.js +1 -0
  29. package/dist/billing-foundation/consumer-protection/termination-pages.d.ts +8 -1
  30. package/dist/billing-foundation/consumer-protection/termination-pages.js +59 -32
  31. package/dist/billing-foundation/consumer-protection/termination-request.js +2 -0
  32. package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -0
  33. package/dist/billing-foundation/consumer-protection/termination-texts.js +3 -1
  34. package/dist/billing-foundation/projection.js +23 -40
  35. package/dist/channel-discord/discord-channel.d.ts +4 -0
  36. package/dist/channel-discord/discord-channel.js +4 -0
  37. package/dist/channel-discord/feature.js +7 -2
  38. package/dist/channel-discord/index.d.ts +1 -1
  39. package/dist/channel-discord/index.js +1 -1
  40. package/dist/channel-email/email-channel.js +1 -0
  41. package/dist/channel-slack/feature.js +4 -2
  42. package/dist/channel-slack/index.d.ts +1 -1
  43. package/dist/channel-slack/index.js +1 -1
  44. package/dist/channel-slack/slack-channel.d.ts +4 -0
  45. package/dist/channel-slack/slack-channel.js +4 -0
  46. package/dist/channel-teams/feature.js +4 -2
  47. package/dist/channel-teams/index.d.ts +1 -1
  48. package/dist/channel-teams/index.js +1 -1
  49. package/dist/channel-teams/teams-channel.d.ts +4 -0
  50. package/dist/channel-teams/teams-channel.js +4 -0
  51. package/dist/channel-telegram/feature.js +4 -2
  52. package/dist/channel-telegram/index.d.ts +1 -1
  53. package/dist/channel-telegram/index.js +1 -1
  54. package/dist/channel-telegram/telegram-channel.d.ts +8 -0
  55. package/dist/channel-telegram/telegram-channel.js +28 -12
  56. package/dist/delivery/chat-webhook-channel.d.ts +5 -1
  57. package/dist/delivery/chat-webhook-channel.js +10 -5
  58. package/dist/delivery/chat-webhook-sender.d.ts +6 -2
  59. package/dist/delivery/chat-webhook-sender.js +13 -8
  60. package/dist/delivery/delivery-service.d.ts +4 -3
  61. package/dist/delivery/delivery-service.js +53 -33
  62. package/dist/delivery/index.d.ts +2 -2
  63. package/dist/delivery/index.js +2 -2
  64. package/dist/delivery/jobs.js +3 -1
  65. package/dist/delivery/types.d.ts +5 -3
  66. package/dist/inbound-mail-foundation/projection.js +37 -45
  67. package/dist/legal-pages/feature.d.ts +3 -15
  68. package/dist/legal-pages/feature.js +20 -50
  69. package/dist/legal-pages/lib/boot-check.d.ts +10 -0
  70. package/dist/legal-pages/lib/boot-check.js +33 -0
  71. package/dist/legal-pages/lib/validate-routes.d.ts +2 -0
  72. package/dist/legal-pages/lib/validate-routes.js +17 -0
  73. package/dist/page-render/index.d.ts +2 -1
  74. package/dist/page-render/index.js +1 -0
  75. package/dist/page-render/layout.d.ts +7 -0
  76. package/dist/page-render/layout.js +5 -0
  77. package/dist/page-render/trailing-slash.d.ts +2 -0
  78. package/dist/page-render/trailing-slash.js +15 -0
  79. package/dist/renderer-simple/feature.js +1 -0
  80. package/dist/renderer-simple/index.d.ts +1 -1
  81. package/dist/renderer-simple/index.js +1 -1
  82. package/dist/renderer-simple/simple-renderer.d.ts +12 -5
  83. package/dist/renderer-simple/simple-renderer.js +66 -12
  84. package/dist/secrets/announce-secret-change.d.ts +8 -0
  85. package/dist/secrets/announce-secret-change.js +18 -0
  86. package/dist/secrets/feature.js +1 -1
  87. package/dist/secrets/handlers/delete.write.js +2 -0
  88. package/dist/secrets/handlers/set.write.js +6 -1
  89. package/dist/secrets/i18n.d.ts +4 -0
  90. package/dist/secrets/i18n.js +4 -0
  91. package/dist/secrets/secrets-context.d.ts +2 -0
  92. package/dist/secrets/secrets-context.js +3 -0
  93. package/dist/secrets/write-gate.d.ts +5 -1
  94. package/dist/secrets/write-gate.js +24 -1
  95. package/dist/tenant/constants.d.ts +1 -0
  96. package/dist/tenant/constants.js +1 -0
  97. package/dist/tenant/handlers/add-member.write.js +14 -3
  98. package/dist/tenant/handlers/invitations.query.d.ts +8 -0
  99. package/dist/tenant/handlers/invitations.query.js +15 -1
  100. package/dist/tenant/index.d.ts +1 -0
  101. package/dist/tenant/index.js +1 -0
  102. package/dist/tenant/invitation-table.d.ts +2 -0
  103. package/dist/tenant/invitation-table.js +10 -1
  104. package/dist/tenant-lifecycle/lifecycle-gate.d.ts +1 -0
  105. package/dist/tenant-lifecycle/lifecycle-gate.js +2 -0
  106. package/dist/tenant-lifecycle/run-tenant-destroy.js +2 -2
  107. package/dist/tier-engine/feature.js +83 -14
  108. package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +3 -3
  109. package/dist/tier-engine/handlers/set-tenant-tier.write.js +17 -10
  110. package/dist/user/handlers/update-roles.js +7 -18
  111. package/dist/user/invited-global-roles.d.ts +14 -0
  112. package/dist/user/invited-global-roles.js +35 -0
  113. package/dist/user-data-rights/feature.js +3 -0
  114. package/dist/waitlist/constants.d.ts +33 -0
  115. package/dist/waitlist/constants.js +35 -0
  116. package/dist/waitlist/entity.d.ts +433 -0
  117. package/dist/waitlist/entity.js +64 -0
  118. package/dist/waitlist/feature.d.ts +3 -0
  119. package/dist/waitlist/feature.js +38 -0
  120. package/dist/waitlist/handlers/invite.write.d.ts +9 -0
  121. package/dist/waitlist/handlers/invite.write.js +119 -0
  122. package/dist/waitlist/handlers/list.query.d.ts +1 -0
  123. package/dist/waitlist/handlers/list.query.js +10 -0
  124. package/dist/waitlist/handlers/reject.write.d.ts +7 -0
  125. package/dist/waitlist/handlers/reject.write.js +80 -0
  126. package/dist/waitlist/handlers/submit.write.d.ts +13 -0
  127. package/dist/waitlist/handlers/submit.write.js +112 -0
  128. package/dist/waitlist/i18n.d.ts +6 -0
  129. package/dist/waitlist/i18n.js +31 -0
  130. package/dist/waitlist/index.d.ts +5 -0
  131. package/dist/waitlist/index.js +4 -0
  132. package/dist/waitlist/lib.d.ts +10 -0
  133. package/dist/waitlist/lib.js +24 -0
  134. package/dist/waitlist/mail.d.ts +15 -0
  135. package/dist/waitlist/mail.js +73 -0
  136. package/dist/waitlist/options.d.ts +29 -0
  137. package/dist/waitlist/options.js +8 -0
  138. package/dist/waitlist/payloads.d.ts +14 -0
  139. package/dist/waitlist/payloads.js +47 -0
  140. package/dist/waitlist/screens.d.ts +2 -0
  141. package/dist/waitlist/screens.js +42 -0
  142. package/dist/waitlist/user-data-hooks.d.ts +3 -0
  143. package/dist/waitlist/user-data-hooks.js +71 -0
  144. package/dist/workflow-runner/db/queries/due-runs.d.ts +4 -5
  145. package/dist/workflow-runner/db/queries/due-runs.js +5 -4
  146. package/dist/workflow-runner/feature.js +4 -6
  147. package/package.json +17 -9
  148. package/src/auth-email-password/changes.json +18 -0
  149. package/src/auth-mfa/changes.json +6 -0
  150. package/src/billing-foundation/changes.json +13 -0
  151. package/src/channel-telegram/changes.json +9 -0
  152. package/src/delivery/changes.json +7 -0
  153. package/src/files/changes.json +13 -1
  154. package/src/inbound-mail-foundation/changes.json +7 -0
  155. package/src/renderer-simple/changes.json +6 -0
  156. package/src/secrets/changes.json +12 -0
  157. package/src/tenant/changes.json +6 -0
  158. package/src/user/changes.json +13 -6
  159. package/src/waitlist/changes.json +17 -0
  160. package/src/workflow-runner/changes.json +7 -0
  161. package/dist/billing-foundation/db/queries/payment-projection.d.ts +0 -2
  162. package/dist/billing-foundation/db/queries/payment-projection.js +0 -13
  163. package/dist/billing-foundation/db/queries/subscription-projection.d.ts +0 -2
  164. package/dist/billing-foundation/db/queries/subscription-projection.js +0 -7
  165. package/dist/inbound-mail-foundation/db/queries/inbound-projections.d.ts +0 -7
  166. package/dist/inbound-mail-foundation/db/queries/inbound-projections.js +0 -24
@@ -0,0 +1,433 @@
1
+ export declare function isOpenWaitlistStatus(status: unknown): boolean;
2
+ export declare const waitlistEntryEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
3
+ name: {
4
+ readonly type: "text";
5
+ readonly description?: string;
6
+ readonly maxLength?: number;
7
+ readonly required?: boolean;
8
+ readonly searchable?: boolean;
9
+ readonly sortable?: boolean;
10
+ readonly filterable?: boolean;
11
+ readonly encrypted?: boolean;
12
+ readonly sensitive?: boolean;
13
+ readonly writeOnly?: boolean;
14
+ readonly readAsInstruction?: boolean;
15
+ readonly format?: "email" | "url" | "phone" | "password";
16
+ readonly default?: string;
17
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
18
+ readonly multiline?: boolean | {
19
+ readonly rows?: number;
20
+ };
21
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
22
+ required: true;
23
+ };
24
+ email: {
25
+ readonly type: "text";
26
+ readonly description?: string;
27
+ readonly maxLength?: number;
28
+ readonly required?: boolean;
29
+ readonly searchable?: boolean;
30
+ readonly sortable?: boolean;
31
+ readonly filterable?: boolean;
32
+ readonly encrypted?: boolean;
33
+ readonly sensitive?: boolean;
34
+ readonly writeOnly?: boolean;
35
+ readonly readAsInstruction?: boolean;
36
+ readonly format?: "email" | "url" | "phone" | "password";
37
+ readonly default?: string;
38
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
39
+ readonly multiline?: boolean | {
40
+ readonly rows?: number;
41
+ };
42
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
43
+ required: true;
44
+ };
45
+ company: {
46
+ readonly type: "text";
47
+ readonly description?: string;
48
+ readonly maxLength?: number;
49
+ readonly required?: boolean;
50
+ readonly searchable?: boolean;
51
+ readonly sortable?: boolean;
52
+ readonly filterable?: boolean;
53
+ readonly encrypted?: boolean;
54
+ readonly sensitive?: boolean;
55
+ readonly writeOnly?: boolean;
56
+ readonly readAsInstruction?: boolean;
57
+ readonly format?: "email" | "url" | "phone" | "password";
58
+ readonly default?: string;
59
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
60
+ readonly multiline?: boolean | {
61
+ readonly rows?: number;
62
+ };
63
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
64
+ required: false;
65
+ };
66
+ portfolio: {
67
+ readonly type: "text";
68
+ readonly description?: string;
69
+ readonly maxLength?: number;
70
+ readonly required?: boolean;
71
+ readonly searchable?: boolean;
72
+ readonly sortable?: boolean;
73
+ readonly filterable?: boolean;
74
+ readonly encrypted?: boolean;
75
+ readonly sensitive?: boolean;
76
+ readonly writeOnly?: boolean;
77
+ readonly readAsInstruction?: boolean;
78
+ readonly format?: "email" | "url" | "phone" | "password";
79
+ readonly default?: string;
80
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
81
+ readonly multiline?: boolean | {
82
+ readonly rows?: number;
83
+ };
84
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
85
+ required: false;
86
+ };
87
+ message: {
88
+ readonly type: "longText";
89
+ readonly description?: string;
90
+ readonly maxLength?: number;
91
+ readonly required?: boolean;
92
+ readonly encrypted?: boolean;
93
+ readonly sensitive?: boolean;
94
+ readonly readAsInstruction?: boolean;
95
+ readonly default?: string;
96
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
97
+ readonly multiline?: boolean | {
98
+ readonly rows?: number;
99
+ };
100
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
101
+ required: false;
102
+ };
103
+ locale: {
104
+ readonly type: "text";
105
+ readonly description?: string;
106
+ readonly maxLength?: number;
107
+ readonly required?: boolean;
108
+ readonly searchable?: boolean;
109
+ readonly sortable?: boolean;
110
+ readonly filterable?: boolean;
111
+ readonly encrypted?: boolean;
112
+ readonly sensitive?: boolean;
113
+ readonly writeOnly?: boolean;
114
+ readonly readAsInstruction?: boolean;
115
+ readonly format?: "email" | "url" | "phone" | "password";
116
+ readonly default?: string;
117
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
118
+ readonly multiline?: boolean | {
119
+ readonly rows?: number;
120
+ };
121
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
122
+ required: true;
123
+ };
124
+ status: {
125
+ readonly type: "select";
126
+ readonly description?: string;
127
+ readonly options: readonly ["pending", "invited", "rejected"];
128
+ readonly required?: boolean;
129
+ readonly sortable?: boolean;
130
+ readonly filterable?: boolean;
131
+ readonly sensitive?: boolean;
132
+ readonly default?: "invited" | "pending" | "rejected" | undefined;
133
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
134
+ readonly display?: "radio" | "dropdown";
135
+ readonly optionTones?: {
136
+ readonly invited?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
137
+ readonly pending?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
138
+ readonly rejected?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
139
+ } | undefined;
140
+ readonly optionsQuery?: string;
141
+ readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
142
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
143
+ required: true;
144
+ };
145
+ submittedAt: {
146
+ readonly type: "timestamp";
147
+ readonly description?: string;
148
+ readonly required?: boolean;
149
+ readonly sortable?: boolean;
150
+ readonly filterable?: boolean;
151
+ readonly sensitive?: boolean;
152
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
153
+ readonly locatedBy?: string;
154
+ readonly min?: string;
155
+ readonly max?: string;
156
+ readonly locale?: string;
157
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
158
+ required: true;
159
+ };
160
+ invitedAt: {
161
+ readonly type: "timestamp";
162
+ readonly description?: string;
163
+ readonly required?: boolean;
164
+ readonly sortable?: boolean;
165
+ readonly filterable?: boolean;
166
+ readonly sensitive?: boolean;
167
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
168
+ readonly locatedBy?: string;
169
+ readonly min?: string;
170
+ readonly max?: string;
171
+ readonly locale?: string;
172
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
173
+ required: false;
174
+ };
175
+ invitedBy: {
176
+ readonly type: "text";
177
+ readonly description?: string;
178
+ readonly maxLength?: number;
179
+ readonly required?: boolean;
180
+ readonly searchable?: boolean;
181
+ readonly sortable?: boolean;
182
+ readonly filterable?: boolean;
183
+ readonly encrypted?: boolean;
184
+ readonly sensitive?: boolean;
185
+ readonly writeOnly?: boolean;
186
+ readonly readAsInstruction?: boolean;
187
+ readonly format?: "email" | "url" | "phone" | "password";
188
+ readonly default?: string;
189
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
190
+ readonly multiline?: boolean | {
191
+ readonly rows?: number;
192
+ };
193
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
194
+ required: false;
195
+ };
196
+ linkedTenantId: {
197
+ readonly type: "text";
198
+ readonly description?: string;
199
+ readonly maxLength?: number;
200
+ readonly required?: boolean;
201
+ readonly searchable?: boolean;
202
+ readonly sortable?: boolean;
203
+ readonly filterable?: boolean;
204
+ readonly encrypted?: boolean;
205
+ readonly sensitive?: boolean;
206
+ readonly writeOnly?: boolean;
207
+ readonly readAsInstruction?: boolean;
208
+ readonly format?: "email" | "url" | "phone" | "password";
209
+ readonly default?: string;
210
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
211
+ readonly multiline?: boolean | {
212
+ readonly rows?: number;
213
+ };
214
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
215
+ required: false;
216
+ };
217
+ }, "global">;
218
+ export declare const waitlistEntryTable: import("@cosmicdrift/kumiko-framework/db").EntityTable<import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
219
+ name: {
220
+ readonly type: "text";
221
+ readonly description?: string;
222
+ readonly maxLength?: number;
223
+ readonly required?: boolean;
224
+ readonly searchable?: boolean;
225
+ readonly sortable?: boolean;
226
+ readonly filterable?: boolean;
227
+ readonly encrypted?: boolean;
228
+ readonly sensitive?: boolean;
229
+ readonly writeOnly?: boolean;
230
+ readonly readAsInstruction?: boolean;
231
+ readonly format?: "email" | "url" | "phone" | "password";
232
+ readonly default?: string;
233
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
234
+ readonly multiline?: boolean | {
235
+ readonly rows?: number;
236
+ };
237
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
238
+ required: true;
239
+ };
240
+ email: {
241
+ readonly type: "text";
242
+ readonly description?: string;
243
+ readonly maxLength?: number;
244
+ readonly required?: boolean;
245
+ readonly searchable?: boolean;
246
+ readonly sortable?: boolean;
247
+ readonly filterable?: boolean;
248
+ readonly encrypted?: boolean;
249
+ readonly sensitive?: boolean;
250
+ readonly writeOnly?: boolean;
251
+ readonly readAsInstruction?: boolean;
252
+ readonly format?: "email" | "url" | "phone" | "password";
253
+ readonly default?: string;
254
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
255
+ readonly multiline?: boolean | {
256
+ readonly rows?: number;
257
+ };
258
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
259
+ required: true;
260
+ };
261
+ company: {
262
+ readonly type: "text";
263
+ readonly description?: string;
264
+ readonly maxLength?: number;
265
+ readonly required?: boolean;
266
+ readonly searchable?: boolean;
267
+ readonly sortable?: boolean;
268
+ readonly filterable?: boolean;
269
+ readonly encrypted?: boolean;
270
+ readonly sensitive?: boolean;
271
+ readonly writeOnly?: boolean;
272
+ readonly readAsInstruction?: boolean;
273
+ readonly format?: "email" | "url" | "phone" | "password";
274
+ readonly default?: string;
275
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
276
+ readonly multiline?: boolean | {
277
+ readonly rows?: number;
278
+ };
279
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
280
+ required: false;
281
+ };
282
+ portfolio: {
283
+ readonly type: "text";
284
+ readonly description?: string;
285
+ readonly maxLength?: number;
286
+ readonly required?: boolean;
287
+ readonly searchable?: boolean;
288
+ readonly sortable?: boolean;
289
+ readonly filterable?: boolean;
290
+ readonly encrypted?: boolean;
291
+ readonly sensitive?: boolean;
292
+ readonly writeOnly?: boolean;
293
+ readonly readAsInstruction?: boolean;
294
+ readonly format?: "email" | "url" | "phone" | "password";
295
+ readonly default?: string;
296
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
297
+ readonly multiline?: boolean | {
298
+ readonly rows?: number;
299
+ };
300
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
301
+ required: false;
302
+ };
303
+ message: {
304
+ readonly type: "longText";
305
+ readonly description?: string;
306
+ readonly maxLength?: number;
307
+ readonly required?: boolean;
308
+ readonly encrypted?: boolean;
309
+ readonly sensitive?: boolean;
310
+ readonly readAsInstruction?: boolean;
311
+ readonly default?: string;
312
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
313
+ readonly multiline?: boolean | {
314
+ readonly rows?: number;
315
+ };
316
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
317
+ required: false;
318
+ };
319
+ locale: {
320
+ readonly type: "text";
321
+ readonly description?: string;
322
+ readonly maxLength?: number;
323
+ readonly required?: boolean;
324
+ readonly searchable?: boolean;
325
+ readonly sortable?: boolean;
326
+ readonly filterable?: boolean;
327
+ readonly encrypted?: boolean;
328
+ readonly sensitive?: boolean;
329
+ readonly writeOnly?: boolean;
330
+ readonly readAsInstruction?: boolean;
331
+ readonly format?: "email" | "url" | "phone" | "password";
332
+ readonly default?: string;
333
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
334
+ readonly multiline?: boolean | {
335
+ readonly rows?: number;
336
+ };
337
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
338
+ required: true;
339
+ };
340
+ status: {
341
+ readonly type: "select";
342
+ readonly description?: string;
343
+ readonly options: readonly ["pending", "invited", "rejected"];
344
+ readonly required?: boolean;
345
+ readonly sortable?: boolean;
346
+ readonly filterable?: boolean;
347
+ readonly sensitive?: boolean;
348
+ readonly default?: "invited" | "pending" | "rejected" | undefined;
349
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
350
+ readonly display?: "radio" | "dropdown";
351
+ readonly optionTones?: {
352
+ readonly invited?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
353
+ readonly pending?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
354
+ readonly rejected?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
355
+ } | undefined;
356
+ readonly optionsQuery?: string;
357
+ readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
358
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
359
+ required: true;
360
+ };
361
+ submittedAt: {
362
+ readonly type: "timestamp";
363
+ readonly description?: string;
364
+ readonly required?: boolean;
365
+ readonly sortable?: boolean;
366
+ readonly filterable?: boolean;
367
+ readonly sensitive?: boolean;
368
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
369
+ readonly locatedBy?: string;
370
+ readonly min?: string;
371
+ readonly max?: string;
372
+ readonly locale?: string;
373
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
374
+ required: true;
375
+ };
376
+ invitedAt: {
377
+ readonly type: "timestamp";
378
+ readonly description?: string;
379
+ readonly required?: boolean;
380
+ readonly sortable?: boolean;
381
+ readonly filterable?: boolean;
382
+ readonly sensitive?: boolean;
383
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
384
+ readonly locatedBy?: string;
385
+ readonly min?: string;
386
+ readonly max?: string;
387
+ readonly locale?: string;
388
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
389
+ required: false;
390
+ };
391
+ invitedBy: {
392
+ readonly type: "text";
393
+ readonly description?: string;
394
+ readonly maxLength?: number;
395
+ readonly required?: boolean;
396
+ readonly searchable?: boolean;
397
+ readonly sortable?: boolean;
398
+ readonly filterable?: boolean;
399
+ readonly encrypted?: boolean;
400
+ readonly sensitive?: boolean;
401
+ readonly writeOnly?: boolean;
402
+ readonly readAsInstruction?: boolean;
403
+ readonly format?: "email" | "url" | "phone" | "password";
404
+ readonly default?: string;
405
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
406
+ readonly multiline?: boolean | {
407
+ readonly rows?: number;
408
+ };
409
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
410
+ required: false;
411
+ };
412
+ linkedTenantId: {
413
+ readonly type: "text";
414
+ readonly description?: string;
415
+ readonly maxLength?: number;
416
+ readonly required?: boolean;
417
+ readonly searchable?: boolean;
418
+ readonly sortable?: boolean;
419
+ readonly filterable?: boolean;
420
+ readonly encrypted?: boolean;
421
+ readonly sensitive?: boolean;
422
+ readonly writeOnly?: boolean;
423
+ readonly readAsInstruction?: boolean;
424
+ readonly format?: "email" | "url" | "phone" | "password";
425
+ readonly default?: string;
426
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
427
+ readonly multiline?: boolean | {
428
+ readonly rows?: number;
429
+ };
430
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
431
+ required: false;
432
+ };
433
+ }, "global">>;
@@ -0,0 +1,64 @@
1
+ import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
2
+ import { createEntity, createLongTextField, createSelectField, createTextField, createTimestampField, } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { WAITLIST_STATUS } from "./constants.js";
4
+ const WAITLIST_STATUS_OPTIONS = [
5
+ WAITLIST_STATUS.Pending,
6
+ WAITLIST_STATUS.Invited,
7
+ WAITLIST_STATUS.Rejected,
8
+ ];
9
+ const OPEN_WAITLIST_STATUSES = [
10
+ WAITLIST_STATUS.Pending,
11
+ WAITLIST_STATUS.Invited,
12
+ ];
13
+ // Entries still awaiting a decision; rejected is terminal.
14
+ export function isOpenWaitlistStatus(status) {
15
+ return OPEN_WAITLIST_STATUSES.some((open) => open === status);
16
+ }
17
+ // An entrant has no user account, so name/email/message are personal: "self" —
18
+ // the row's own id is its crypto-shredding subject. find: "fuzzy" keeps the
19
+ // blind-index equality lookup (dedupe, GDPR export by email) and full-text
20
+ // search alive; sortable stays off, the boot validator rejects it on subject fields.
21
+ // tenancy "global" + systemStream: entries are platform-wide and live on
22
+ // SYSTEM_TENANT_ID, which also lets the per-tenant GDPR hooks read them.
23
+ export const waitlistEntryEntity = createEntity({
24
+ table: "read_waitlist_entries",
25
+ description: "A prospect's signup on the platform waitlist, tracked through pending, invited and rejected status as admins process it; name, email and message are the entry's own personal data.",
26
+ systemStream: true,
27
+ tenancy: "global",
28
+ fields: {
29
+ name: createTextField({ required: true, personal: "self", find: "fuzzy", searchable: true }),
30
+ email: createTextField({ required: true, personal: "self", find: "fuzzy", searchable: true }),
31
+ company: createTextField({
32
+ personal: false,
33
+ reason: "is_business_data",
34
+ searchable: true,
35
+ sortable: true,
36
+ }),
37
+ portfolio: createTextField({ personal: false, reason: "is_business_data" }),
38
+ message: createLongTextField({ personal: "self", find: "none" }),
39
+ locale: createTextField({
40
+ required: true,
41
+ personal: false,
42
+ reason: "is_ui_language_preference",
43
+ sortable: true,
44
+ }),
45
+ status: createSelectField({
46
+ options: WAITLIST_STATUS_OPTIONS,
47
+ required: true,
48
+ default: WAITLIST_STATUS.Pending,
49
+ filterable: true,
50
+ sortable: true,
51
+ }),
52
+ submittedAt: createTimestampField({
53
+ personal: false,
54
+ sortable: true,
55
+ required: true,
56
+ reason: "is_submission_timestamp",
57
+ }),
58
+ invitedAt: createTimestampField(),
59
+ invitedBy: createTextField({ personal: false, reason: "is_admin_audit_id" }),
60
+ // Not named tenantId: that is the framework's physical tenant column.
61
+ linkedTenantId: createTextField({ personal: false, reason: "is_tenant_reference" }),
62
+ },
63
+ });
64
+ export const waitlistEntryTable = buildEntityTable("waitlistEntry", waitlistEntryEntity);
@@ -0,0 +1,3 @@
1
+ import { type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
2
+ import type { WaitlistOptions } from "./options.js";
3
+ export declare function createWaitlistFeature(opts?: WaitlistOptions): FeatureDefinition;
@@ -0,0 +1,38 @@
1
+ import { defineFeature, EXT_USER_DATA, i18nKey, } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { WAITLIST_FEATURE } from "./constants.js";
3
+ import { waitlistEntryEntity } from "./entity.js";
4
+ import { createInviteHandler } from "./handlers/invite.write.js";
5
+ import { listQuery } from "./handlers/list.query.js";
6
+ import { rejectHandler } from "./handlers/reject.write.js";
7
+ import { createSubmitHandler } from "./handlers/submit.write.js";
8
+ import { WAITLIST_I18N } from "./i18n.js";
9
+ import { adminAccess } from "./lib.js";
10
+ import { waitlistListScreen } from "./screens.js";
11
+ import { waitlistEntryDeleteHook, waitlistEntryExportHook } from "./user-data-hooks.js";
12
+ export function createWaitlistFeature(opts = {}) {
13
+ return defineFeature(WAITLIST_FEATURE, (r) => {
14
+ r.describe("Public waitlist: an anonymous, rate-limited `submit` handler collects name, email and optional company, portfolio and message (a honeypot field and per-email dedupe keep bots and repeat submits out, and the response never reveals whether an address was already listed), and mails the submitter a confirmation. SystemAdmins review entries in a list screen and invite or reject them; an invite creates the entrant's own tenant (or joins one shared tenant) and mails an accept link with the configured membership role, never a global role. Entries are platform-wide, their personal data is crypto-shredding-ready, and the EXT_USER_DATA hooks export and erase entries matching a user's email.");
15
+ r.uiHints({ displayLabel: "Waitlist", category: "data", recommended: false });
16
+ r.systemScope();
17
+ r.requires("tenant", "auth-email-password", "user-data-rights");
18
+ r.translations({ keys: WAITLIST_I18N });
19
+ r.entity("waitlistEntry", waitlistEntryEntity);
20
+ r.writeHandler(createSubmitHandler(opts));
21
+ r.writeHandler(createInviteHandler(opts.invite));
22
+ r.writeHandler(rejectHandler);
23
+ r.queryHandler(listQuery);
24
+ r.screen(waitlistListScreen);
25
+ r.nav({
26
+ id: "waitlist",
27
+ label: i18nKey("waitlist.nav.waitlist"),
28
+ screen: "waitlist:screen:waitlist-list",
29
+ icon: "users",
30
+ order: 15,
31
+ access: adminAccess,
32
+ });
33
+ r.useExtension(EXT_USER_DATA, "waitlistEntry", {
34
+ export: waitlistEntryExportHook,
35
+ delete: waitlistEntryDeleteHook,
36
+ });
37
+ });
38
+ }
@@ -0,0 +1,9 @@
1
+ import type { WaitlistInviteOptions } from "../options.js";
2
+ export type WaitlistInviteData = {
3
+ readonly kind: "invited";
4
+ readonly id: string;
5
+ readonly tenantId: string;
6
+ };
7
+ export declare function createInviteHandler(invite?: WaitlistInviteOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"invite", import("zod").ZodObject<{
8
+ id: import("zod").ZodUUID;
9
+ }, import("zod/v4/core").$strict>, WaitlistInviteData, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -0,0 +1,119 @@
1
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import { createSystemUser, defineWriteHandler, parseTenantId, } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { InternalError, NotFoundError, UnprocessableError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
4
+ import { Temporal } from "temporal-polyfill";
5
+ import { AuthErrors, AuthHandlers } from "../../auth-email-password/index.js";
6
+ import { TenantHandlers, tenantTable } from "../../tenant/index.js";
7
+ import { DEFAULT_OWN_TENANT_INVITE_ROLE, WAITLIST_STATUS, WaitlistErrors } from "../constants.js";
8
+ import { isOpenWaitlistStatus } from "../entity.js";
9
+ import { adminAccess, platformActor, waitlistDb, waitlistExecutor } from "../lib.js";
10
+ import { WaitlistIdSchema } from "../payloads.js";
11
+ const DEFAULT_INVITE = {
12
+ mode: "own-tenant",
13
+ role: DEFAULT_OWN_TENANT_INVITE_ROLE,
14
+ };
15
+ // Derived from the entry id so a retry finds the tenant a half-failed run created.
16
+ function tenantKeyForEntry(entryId) {
17
+ return `waitlist-${entryId}`;
18
+ }
19
+ function readEntryRow(row) {
20
+ const { id, version, email, name, company, linkedTenantId } = row;
21
+ if (typeof id !== "string" ||
22
+ typeof version !== "number" ||
23
+ typeof email !== "string" ||
24
+ typeof name !== "string") {
25
+ throw new InternalError({ message: "waitlist:invite: malformed waitlist row" });
26
+ }
27
+ return {
28
+ id,
29
+ version,
30
+ status: row["status"],
31
+ email,
32
+ name,
33
+ company: typeof company === "string" && company !== "" ? company : null,
34
+ linkedTenantId: typeof linkedTenantId === "string" ? linkedTenantId : null,
35
+ };
36
+ }
37
+ function isInviteAlreadyAccepted(details) {
38
+ return (typeof details === "object" &&
39
+ details !== null &&
40
+ "reason" in details &&
41
+ details.reason === AuthErrors.inviteAlreadyAccepted);
42
+ }
43
+ async function ensureOwnTenant(entry, ctx) {
44
+ const linked = entry.linkedTenantId === null ? null : parseTenantId(entry.linkedTenantId);
45
+ if (linked)
46
+ return linked;
47
+ const key = tenantKeyForEntry(entry.id);
48
+ const existing = await fetchOne(waitlistDb(ctx), tenantTable, { key });
49
+ const existingId = existing ? parseTenantId(existing.id) : null;
50
+ if (existingId)
51
+ return existingId;
52
+ const newTenantId = parseTenantId(crypto.randomUUID());
53
+ if (!newTenantId)
54
+ throw new InternalError({ message: "waitlist:invite: tenant id mint failed" });
55
+ const created = await ctx.writeAs(createSystemUser(newTenantId), TenantHandlers.create, {
56
+ id: newTenantId,
57
+ key,
58
+ name: entry.company ?? entry.name,
59
+ });
60
+ return created.isSuccess ? newTenantId : created;
61
+ }
62
+ async function resolveInviteTarget(invite, entry, ctx) {
63
+ if (invite.mode === "shared-tenant")
64
+ return { tenantId: invite.tenantId, role: invite.role };
65
+ const tenantId = await ensureOwnTenant(entry, ctx);
66
+ if (typeof tenantId !== "string")
67
+ return tenantId;
68
+ return { tenantId, role: invite.role ?? DEFAULT_OWN_TENANT_INVITE_ROLE };
69
+ }
70
+ export function createInviteHandler(invite = DEFAULT_INVITE) {
71
+ return defineWriteHandler({
72
+ name: "invite",
73
+ schema: WaitlistIdSchema,
74
+ access: adminAccess,
75
+ description: "Invites a pending or already-invited waitlist entry: creates its tenant (or uses the configured shared tenant), mails the entrant a fresh accept link with the configured role and marks the entry invited; rejected entries cannot be invited. The invitee never receives a global role.",
76
+ // Sends an invite link by email, which cannot be recalled.
77
+ agent: { risk: "high" },
78
+ handler: async (event, ctx) => {
79
+ const db = waitlistDb(ctx);
80
+ const row = await waitlistExecutor.detail({ id: event.payload.id }, platformActor(), db);
81
+ if (!row)
82
+ return writeFailure(new NotFoundError("waitlistEntry", event.payload.id));
83
+ const entry = readEntryRow(row);
84
+ if (!isOpenWaitlistStatus(entry.status)) {
85
+ return writeFailure(new UnprocessableError(WaitlistErrors.notInvitable));
86
+ }
87
+ const target = await resolveInviteTarget(invite, entry, ctx);
88
+ if ("isSuccess" in target)
89
+ return target;
90
+ // inviteCreate is admin-only (no system role); the system variant with an
91
+ // empty globalRoles list is the only in-process path, and a waitlist
92
+ // invite must never grant a global role.
93
+ const invited = await ctx.writeAs(createSystemUser(target.tenantId), AuthHandlers.systemInviteCreate, { email: entry.email.toLowerCase(), role: target.role, globalRoles: [] });
94
+ if (!invited.isSuccess) {
95
+ // Re-inviting a person who already accepted would reset their live invitation.
96
+ if (isInviteAlreadyAccepted(invited.error.details)) {
97
+ return writeFailure(new UnprocessableError(WaitlistErrors.notInvitable));
98
+ }
99
+ return invited;
100
+ }
101
+ const updated = await waitlistExecutor.update({
102
+ id: entry.id,
103
+ version: entry.version,
104
+ changes: {
105
+ status: WAITLIST_STATUS.Invited,
106
+ invitedAt: Temporal.Now.instant(),
107
+ invitedBy: event.user.id,
108
+ linkedTenantId: target.tenantId,
109
+ },
110
+ }, platformActor(), db);
111
+ if (!updated.isSuccess)
112
+ return updated;
113
+ return {
114
+ isSuccess: true,
115
+ data: { kind: "invited", id: entry.id, tenantId: target.tenantId },
116
+ };
117
+ },
118
+ });
119
+ }
@@ -0,0 +1 @@
1
+ export declare const listQuery: import("@cosmicdrift/kumiko-types/handlers").QueryHandlerDef;