@odla-ai/chapter 0.11.0 → 0.12.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.
@@ -184,18 +184,28 @@ async function createClerkUser(secretKey, input, fetchImpl = fetch) {
184
184
  }
185
185
 
186
186
  // src/member.ts
187
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
188
+ function isValidEmail(value) {
189
+ return typeof value === "string" && EMAIL_RE.test(value);
190
+ }
191
+ function clampArray(value, max) {
192
+ if (!Array.isArray(value)) return value;
193
+ return value.filter((x) => typeof x === "string" || typeof x === "number" || typeof x === "boolean").slice(0, max);
194
+ }
187
195
  function hasDisclaimerAck(fields) {
188
196
  return fields.disclaimerAck === true || fields.disclaimerAck === "true";
189
197
  }
190
198
  var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
191
199
  function applicantProfile(chapter, fields) {
200
+ const app = chapter.application;
201
+ const allowed = (f) => app.profileFields === null || app.profileFields.includes(f);
192
202
  const profile = {};
193
- for (const f of [...chapter.application.required, ...chapter.application.optional]) {
194
- if (IDENTITY_FIELDS.has(f)) continue;
203
+ for (const f of [...app.required, ...app.optional]) {
204
+ if (IDENTITY_FIELDS.has(f) || !allowed(f)) continue;
195
205
  const v = fields[f];
196
206
  if (typeof v === "string" && v.trim() !== "") profile[f] = v.trim();
197
207
  }
198
- if (fields.focus !== void 0) profile.focus = fields.focus;
208
+ if (fields.focus !== void 0 && allowed("focus")) profile.focus = clampArray(fields.focus, app.maxArrayLen);
199
209
  return Object.keys(profile).length > 0 ? profile : void 0;
200
210
  }
201
211
  async function submitApplication(db, chapter, fields, opts) {
@@ -209,6 +219,9 @@ async function submitApplication(db, chapter, fields, opts) {
209
219
  const cap = app.maxLen[f] ?? app.defaultMaxLen;
210
220
  if (typeof v === "string" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };
211
221
  }
222
+ if (app.validateEmail && typeof fields.email === "string" && !isValidEmail(fields.email)) {
223
+ return { ok: false, error: "email must be a valid email address" };
224
+ }
212
225
  const acked = hasDisclaimerAck(fields);
213
226
  if (app.requireDisclaimerAck && !acked) {
214
227
  return { ok: false, error: "disclaimerAck is required" };
@@ -218,7 +231,7 @@ async function submitApplication(db, chapter, fields, opts) {
218
231
  for (const f of [...app.required, ...app.optional]) {
219
232
  if (typeof fields[f] === "string") row[f] = fields[f].trim();
220
233
  }
221
- if (fields.focus !== void 0) row.focus = fields.focus;
234
+ if (fields.focus !== void 0) row.focus = clampArray(fields.focus, app.maxArrayLen);
222
235
  if (opts.groupId) row.groupId = opts.groupId;
223
236
  if (acked) row.disclaimerAckAt = opts.now;
224
237
  const { duplicate } = await db.transact(
@@ -271,7 +284,7 @@ async function projectSharedRecord(deps, person) {
271
284
  return upsertPerson(deps, { email: person.email, input: sharedPersonInput(person), mutationId: `share:${person.hubRecordId}` });
272
285
  }
273
286
  async function projectApplicant(deps, applicant) {
274
- const input = sharedPersonInput({
287
+ const base = sharedPersonInput({
275
288
  email: applicant.email,
276
289
  firstName: applicant.firstName,
277
290
  lastName: applicant.lastName,
@@ -279,7 +292,14 @@ async function projectApplicant(deps, applicant) {
279
292
  linkedin: applicant.linkedin,
280
293
  hubRecordId: applicant.applicationId
281
294
  });
282
- return upsertPerson(deps, { email: applicant.email, input, mutationId: `apply:${applicant.applicationId}` });
295
+ const mutationId = `apply:${applicant.applicationId}`;
296
+ const extra = applicant.extra ?? {};
297
+ if (Object.keys(extra).length === 0) return upsertPerson(deps, { email: applicant.email, input: base, mutationId });
298
+ try {
299
+ return await upsertPerson(deps, { email: applicant.email, input: { ...base, ...extra }, mutationId });
300
+ } catch {
301
+ return upsertPerson(deps, { email: applicant.email, input: base, mutationId });
302
+ }
283
303
  }
284
304
 
285
305
  // src/scheduling.ts
@@ -523,10 +543,14 @@ async function provisionApplicant(db, chapter, applicationId, fields) {
523
543
  const email = typeof fields.email === "string" ? fields.email : "";
524
544
  if (!email) return;
525
545
  const s = (v) => typeof v === "string" ? v : void 0;
546
+ const extra = {};
547
+ for (const f of chapter.application.crmFields) {
548
+ if (fields[f] !== void 0) extra[f] = fields[f];
549
+ }
526
550
  try {
527
551
  await projectApplicant(
528
552
  { crm: chapter.crm, db, now: () => Date.now(), newId: () => crypto.randomUUID() },
529
- { applicationId, email, firstName: s(fields.firstName), lastName: s(fields.lastName), phone: s(fields.phone), linkedin: s(fields.linkedin) }
553
+ { applicationId, email, firstName: s(fields.firstName), lastName: s(fields.lastName), phone: s(fields.phone), linkedin: s(fields.linkedin), extra }
530
554
  );
531
555
  } catch {
532
556
  }