@odla-ai/chapter 0.7.0 → 0.8.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.
@@ -189,7 +189,14 @@ interface ChapterConfig {
189
189
  auth?: ChapterAuth;
190
190
  /** odla services (db implied). Default `["db","calendar","o11y"]`. */
191
191
  services?: readonly string[];
192
+ /** Apply-time account provisioning: `"invite"` (default) mints a Clerk
193
+ * invitation, `"create"` makes the account server-side (so join can say the
194
+ * account is ready), `"none"` skips it. Any of these needs a `clerk_secret_key`
195
+ * vault secret to act. */
196
+ account?: AccountModel;
192
197
  }
198
+ /** Apply-time Clerk account provisioning model. */
199
+ type AccountModel = "invite" | "create" | "none";
193
200
  /** The resolved engine `defineChapter()` returns. */
194
201
  interface Chapter {
195
202
  config: ChapterConfig;
@@ -209,6 +216,8 @@ interface Chapter {
209
216
  schema: DbSchema;
210
217
  rules: DbRules;
211
218
  services: readonly string[];
219
+ /** Resolved apply-time account provisioning model (default `"invite"`). */
220
+ account: AccountModel;
212
221
  /** The seed `groups` row derived from config (chapter mode), else `null`. */
213
222
  groupSeed(): Record<string, unknown> | null;
214
223
  }
@@ -189,7 +189,14 @@ interface ChapterConfig {
189
189
  auth?: ChapterAuth;
190
190
  /** odla services (db implied). Default `["db","calendar","o11y"]`. */
191
191
  services?: readonly string[];
192
+ /** Apply-time account provisioning: `"invite"` (default) mints a Clerk
193
+ * invitation, `"create"` makes the account server-side (so join can say the
194
+ * account is ready), `"none"` skips it. Any of these needs a `clerk_secret_key`
195
+ * vault secret to act. */
196
+ account?: AccountModel;
192
197
  }
198
+ /** Apply-time Clerk account provisioning model. */
199
+ type AccountModel = "invite" | "create" | "none";
193
200
  /** The resolved engine `defineChapter()` returns. */
194
201
  interface Chapter {
195
202
  config: ChapterConfig;
@@ -209,6 +216,8 @@ interface Chapter {
209
216
  schema: DbSchema;
210
217
  rules: DbRules;
211
218
  services: readonly string[];
219
+ /** Resolved apply-time account provisioning model (default `"invite"`). */
220
+ account: AccountModel;
212
221
  /** The seed `groups` row derived from config (chapter mode), else `null`. */
213
222
  groupSeed(): Record<string, unknown> | null;
214
223
  }
@@ -114,6 +114,26 @@ async function createClerkInvitation(secretKey, input, fetchImpl = fetch) {
114
114
  });
115
115
  return { ok: res.ok, status: res.status };
116
116
  }
117
+ function clerkUserRequest(input) {
118
+ return {
119
+ path: "/v1/users",
120
+ body: {
121
+ email_address: [input.email],
122
+ skip_password_requirement: true,
123
+ ...input.firstName ? { first_name: input.firstName } : {},
124
+ ...input.lastName ? { last_name: input.lastName } : {}
125
+ }
126
+ };
127
+ }
128
+ async function createClerkUser(secretKey, input, fetchImpl = fetch) {
129
+ const { path, body } = clerkUserRequest(input);
130
+ const res = await fetchImpl(`https://api.clerk.com${path}`, {
131
+ method: "POST",
132
+ headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
133
+ body: JSON.stringify(body)
134
+ });
135
+ return { ok: res.ok, status: res.status };
136
+ }
117
137
 
118
138
  // src/member.ts
119
139
  async function submitApplication(db, chapter, fields, opts) {
@@ -428,10 +448,15 @@ async function provisionApplicant(db, chapter, applicationId, fields) {
428
448
  );
429
449
  } catch {
430
450
  }
431
- try {
432
- const secret = await getVaultSecret(db, "clerk_secret_key");
433
- if (secret) await createClerkInvitation(secret, { email });
434
- } catch {
451
+ if (chapter.account !== "none") {
452
+ try {
453
+ const secret = await getVaultSecret(db, "clerk_secret_key");
454
+ if (secret) {
455
+ if (chapter.account === "create") await createClerkUser(secret, { email, firstName: s(fields.firstName), lastName: s(fields.lastName) });
456
+ else await createClerkInvitation(secret, { email });
457
+ }
458
+ } catch {
459
+ }
435
460
  }
436
461
  }
437
462
  async function notifyAdminOfApplication(db, env, chapterId, applicationId, fields) {