@odla-ai/chapter 0.9.0 → 0.10.1
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.
- package/README.md +48 -14
- package/dist/index.cjs +68 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -2
- package/dist/index.d.ts +47 -2
- package/dist/index.js +68 -13
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +118 -29
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +17 -0
- package/dist/worker/index.d.ts +17 -0
- package/dist/worker/index.js +118 -29
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -5
package/dist/index.d.cts
CHANGED
|
@@ -215,9 +215,24 @@ interface ChapterConfig {
|
|
|
215
215
|
* account is ready), `"none"` skips it. Any of these needs a `clerk_secret_key`
|
|
216
216
|
* vault secret to act. */
|
|
217
217
|
account?: AccountModel;
|
|
218
|
+
/** WHEN lifecycle email fires. Addressing and content live on the group row
|
|
219
|
+
* (owner-editable at runtime); this is the trigger, which is a build-time
|
|
220
|
+
* decision. See {@link ChapterSends}. */
|
|
221
|
+
sends?: ChapterSends;
|
|
218
222
|
}
|
|
219
223
|
/** Apply-time Clerk account provisioning model. */
|
|
220
224
|
type AccountModel = "invite" | "create" | "none";
|
|
225
|
+
/** When the admin notification fires: on application `submit` (default), on the
|
|
226
|
+
* first successful `payment`, or `never` (the site drives it itself). */
|
|
227
|
+
type AdminNotificationTrigger = "submit" | "payment" | "never";
|
|
228
|
+
/** Send-policy config: the trigger for each lifecycle email chapter owns. */
|
|
229
|
+
interface ChapterSends {
|
|
230
|
+
adminNotification?: AdminNotificationTrigger;
|
|
231
|
+
}
|
|
232
|
+
/** Resolved send policy (every trigger present). */
|
|
233
|
+
interface ResolvedSends {
|
|
234
|
+
adminNotification: AdminNotificationTrigger;
|
|
235
|
+
}
|
|
221
236
|
/** The resolved engine `defineChapter()` returns. */
|
|
222
237
|
interface Chapter {
|
|
223
238
|
config: ChapterConfig;
|
|
@@ -239,6 +254,8 @@ interface Chapter {
|
|
|
239
254
|
services: readonly string[];
|
|
240
255
|
/** Resolved apply-time account provisioning model (default `"invite"`). */
|
|
241
256
|
account: AccountModel;
|
|
257
|
+
/** Resolved send policy — when each lifecycle email fires. */
|
|
258
|
+
sends: ResolvedSends;
|
|
242
259
|
/** The seed `groups` row derived from config (chapter mode), else `null`. */
|
|
243
260
|
groupSeed(): Record<string, unknown> | null;
|
|
244
261
|
}
|
|
@@ -608,6 +625,14 @@ declare function canceledPatch(): {
|
|
|
608
625
|
|
|
609
626
|
/** Apply defaults + validate the application config. Throws at import on bad shape. */
|
|
610
627
|
declare function resolveApplication(a: ChapterApplication | undefined): ResolvedApplication;
|
|
628
|
+
/**
|
|
629
|
+
* The applicant profile written to the Clerk account's `public_metadata.profile`:
|
|
630
|
+
* every configured application field Clerk does not already carry natively, plus
|
|
631
|
+
* `focus`. Derived from `application.required`/`optional`, so a site's own field
|
|
632
|
+
* names project without this package knowing them. Pure; returns `undefined` when
|
|
633
|
+
* there is nothing to write.
|
|
634
|
+
*/
|
|
635
|
+
declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
611
636
|
/** A validated submission, or a 400-worthy validation error the route returns. */
|
|
612
637
|
type SubmitResult = {
|
|
613
638
|
ok: true;
|
|
@@ -708,6 +733,8 @@ interface ClerkResult {
|
|
|
708
733
|
ok: boolean;
|
|
709
734
|
status: number;
|
|
710
735
|
existed?: boolean;
|
|
736
|
+
/** Set when an `existed` heal also refreshed the account's public_metadata. */
|
|
737
|
+
refreshed?: boolean;
|
|
711
738
|
}
|
|
712
739
|
/** Inputs for a Clerk invitation. */
|
|
713
740
|
interface ClerkInviteInput {
|
|
@@ -746,7 +773,9 @@ declare function clerkUserRequest(input: ClerkUserInput): {
|
|
|
746
773
|
};
|
|
747
774
|
/** Create the applicant's Clerk account server-side. A repeat for an email that
|
|
748
775
|
* already has an account heals to `{ ok: true, existed: true }` — the account
|
|
749
|
-
* exists, which is the state apply-time provisioning wanted
|
|
776
|
+
* exists, which is the state apply-time provisioning wanted — and, when
|
|
777
|
+
* `publicMetadata` was supplied, refreshes it on the existing account so a
|
|
778
|
+
* re-application repairs a previously missed create (`refreshed: true`). */
|
|
750
779
|
declare function createClerkUser(secretKey: string, input: ClerkUserInput, fetchImpl?: typeof fetch): Promise<ClerkResult>;
|
|
751
780
|
|
|
752
781
|
/** An application row, as far as the session cares about it. */
|
|
@@ -888,6 +917,22 @@ declare const SCHEDULING_DEFAULTS: ResolvedScheduling;
|
|
|
888
917
|
* `windowDays` is capped at 62 because Google FreeBusy is.
|
|
889
918
|
*/
|
|
890
919
|
declare function resolveScheduling(config?: ChapterScheduling): ResolvedScheduling;
|
|
920
|
+
/** Validation messages keyed by form field. */
|
|
921
|
+
type SchedulingErrors = Record<string, string>;
|
|
922
|
+
/**
|
|
923
|
+
* Validate a scheduling config field by field, collecting owner-readable messages
|
|
924
|
+
* rather than throwing on the first problem. An admin settings form needs to say
|
|
925
|
+
* *which* field is wrong and why — "pick at least one day" beats a stack trace —
|
|
926
|
+
* so the admin route returns these directly. {@link resolveScheduling} is the
|
|
927
|
+
* throwing wrapper for internal/config-time use.
|
|
928
|
+
*/
|
|
929
|
+
declare function validateScheduling(config?: ChapterScheduling): {
|
|
930
|
+
ok: true;
|
|
931
|
+
value: ResolvedScheduling;
|
|
932
|
+
} | {
|
|
933
|
+
ok: false;
|
|
934
|
+
errors: SchedulingErrors;
|
|
935
|
+
};
|
|
891
936
|
/** Statuses a member may book/reschedule from (early pipeline only). */
|
|
892
937
|
/** The availability window: `[now, now + windowDays]` in epoch ms. */
|
|
893
938
|
declare function slotWindow(now: number, windowDays: number): {
|
|
@@ -971,4 +1016,4 @@ type ApplicationBookingPatch = {
|
|
|
971
1016
|
* already there (never backward). */
|
|
972
1017
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
973
1018
|
|
|
974
|
-
export { type AccountModel, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, verifyStripeSignature, webhookMutationId };
|
|
1019
|
+
export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, validateScheduling, verifyStripeSignature, webhookMutationId };
|
package/dist/index.d.ts
CHANGED
|
@@ -215,9 +215,24 @@ interface ChapterConfig {
|
|
|
215
215
|
* account is ready), `"none"` skips it. Any of these needs a `clerk_secret_key`
|
|
216
216
|
* vault secret to act. */
|
|
217
217
|
account?: AccountModel;
|
|
218
|
+
/** WHEN lifecycle email fires. Addressing and content live on the group row
|
|
219
|
+
* (owner-editable at runtime); this is the trigger, which is a build-time
|
|
220
|
+
* decision. See {@link ChapterSends}. */
|
|
221
|
+
sends?: ChapterSends;
|
|
218
222
|
}
|
|
219
223
|
/** Apply-time Clerk account provisioning model. */
|
|
220
224
|
type AccountModel = "invite" | "create" | "none";
|
|
225
|
+
/** When the admin notification fires: on application `submit` (default), on the
|
|
226
|
+
* first successful `payment`, or `never` (the site drives it itself). */
|
|
227
|
+
type AdminNotificationTrigger = "submit" | "payment" | "never";
|
|
228
|
+
/** Send-policy config: the trigger for each lifecycle email chapter owns. */
|
|
229
|
+
interface ChapterSends {
|
|
230
|
+
adminNotification?: AdminNotificationTrigger;
|
|
231
|
+
}
|
|
232
|
+
/** Resolved send policy (every trigger present). */
|
|
233
|
+
interface ResolvedSends {
|
|
234
|
+
adminNotification: AdminNotificationTrigger;
|
|
235
|
+
}
|
|
221
236
|
/** The resolved engine `defineChapter()` returns. */
|
|
222
237
|
interface Chapter {
|
|
223
238
|
config: ChapterConfig;
|
|
@@ -239,6 +254,8 @@ interface Chapter {
|
|
|
239
254
|
services: readonly string[];
|
|
240
255
|
/** Resolved apply-time account provisioning model (default `"invite"`). */
|
|
241
256
|
account: AccountModel;
|
|
257
|
+
/** Resolved send policy — when each lifecycle email fires. */
|
|
258
|
+
sends: ResolvedSends;
|
|
242
259
|
/** The seed `groups` row derived from config (chapter mode), else `null`. */
|
|
243
260
|
groupSeed(): Record<string, unknown> | null;
|
|
244
261
|
}
|
|
@@ -608,6 +625,14 @@ declare function canceledPatch(): {
|
|
|
608
625
|
|
|
609
626
|
/** Apply defaults + validate the application config. Throws at import on bad shape. */
|
|
610
627
|
declare function resolveApplication(a: ChapterApplication | undefined): ResolvedApplication;
|
|
628
|
+
/**
|
|
629
|
+
* The applicant profile written to the Clerk account's `public_metadata.profile`:
|
|
630
|
+
* every configured application field Clerk does not already carry natively, plus
|
|
631
|
+
* `focus`. Derived from `application.required`/`optional`, so a site's own field
|
|
632
|
+
* names project without this package knowing them. Pure; returns `undefined` when
|
|
633
|
+
* there is nothing to write.
|
|
634
|
+
*/
|
|
635
|
+
declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
611
636
|
/** A validated submission, or a 400-worthy validation error the route returns. */
|
|
612
637
|
type SubmitResult = {
|
|
613
638
|
ok: true;
|
|
@@ -708,6 +733,8 @@ interface ClerkResult {
|
|
|
708
733
|
ok: boolean;
|
|
709
734
|
status: number;
|
|
710
735
|
existed?: boolean;
|
|
736
|
+
/** Set when an `existed` heal also refreshed the account's public_metadata. */
|
|
737
|
+
refreshed?: boolean;
|
|
711
738
|
}
|
|
712
739
|
/** Inputs for a Clerk invitation. */
|
|
713
740
|
interface ClerkInviteInput {
|
|
@@ -746,7 +773,9 @@ declare function clerkUserRequest(input: ClerkUserInput): {
|
|
|
746
773
|
};
|
|
747
774
|
/** Create the applicant's Clerk account server-side. A repeat for an email that
|
|
748
775
|
* already has an account heals to `{ ok: true, existed: true }` — the account
|
|
749
|
-
* exists, which is the state apply-time provisioning wanted
|
|
776
|
+
* exists, which is the state apply-time provisioning wanted — and, when
|
|
777
|
+
* `publicMetadata` was supplied, refreshes it on the existing account so a
|
|
778
|
+
* re-application repairs a previously missed create (`refreshed: true`). */
|
|
750
779
|
declare function createClerkUser(secretKey: string, input: ClerkUserInput, fetchImpl?: typeof fetch): Promise<ClerkResult>;
|
|
751
780
|
|
|
752
781
|
/** An application row, as far as the session cares about it. */
|
|
@@ -888,6 +917,22 @@ declare const SCHEDULING_DEFAULTS: ResolvedScheduling;
|
|
|
888
917
|
* `windowDays` is capped at 62 because Google FreeBusy is.
|
|
889
918
|
*/
|
|
890
919
|
declare function resolveScheduling(config?: ChapterScheduling): ResolvedScheduling;
|
|
920
|
+
/** Validation messages keyed by form field. */
|
|
921
|
+
type SchedulingErrors = Record<string, string>;
|
|
922
|
+
/**
|
|
923
|
+
* Validate a scheduling config field by field, collecting owner-readable messages
|
|
924
|
+
* rather than throwing on the first problem. An admin settings form needs to say
|
|
925
|
+
* *which* field is wrong and why — "pick at least one day" beats a stack trace —
|
|
926
|
+
* so the admin route returns these directly. {@link resolveScheduling} is the
|
|
927
|
+
* throwing wrapper for internal/config-time use.
|
|
928
|
+
*/
|
|
929
|
+
declare function validateScheduling(config?: ChapterScheduling): {
|
|
930
|
+
ok: true;
|
|
931
|
+
value: ResolvedScheduling;
|
|
932
|
+
} | {
|
|
933
|
+
ok: false;
|
|
934
|
+
errors: SchedulingErrors;
|
|
935
|
+
};
|
|
891
936
|
/** Statuses a member may book/reschedule from (early pipeline only). */
|
|
892
937
|
/** The availability window: `[now, now + windowDays]` in epoch ms. */
|
|
893
938
|
declare function slotWindow(now: number, windowDays: number): {
|
|
@@ -971,4 +1016,4 @@ type ApplicationBookingPatch = {
|
|
|
971
1016
|
* already there (never backward). */
|
|
972
1017
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
973
1018
|
|
|
974
|
-
export { type AccountModel, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, verifyStripeSignature, webhookMutationId };
|
|
1019
|
+
export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, validateScheduling, verifyStripeSignature, webhookMutationId };
|
package/dist/index.js
CHANGED
|
@@ -403,6 +403,17 @@ function resolveApplication(a) {
|
|
|
403
403
|
bodyCap: a?.bodyCap ?? 32768
|
|
404
404
|
};
|
|
405
405
|
}
|
|
406
|
+
var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
|
|
407
|
+
function applicantProfile(chapter, fields) {
|
|
408
|
+
const profile = {};
|
|
409
|
+
for (const f of [...chapter.application.required, ...chapter.application.optional]) {
|
|
410
|
+
if (IDENTITY_FIELDS.has(f)) continue;
|
|
411
|
+
const v = fields[f];
|
|
412
|
+
if (typeof v === "string" && v.trim() !== "") profile[f] = v.trim();
|
|
413
|
+
}
|
|
414
|
+
if (fields.focus !== void 0) profile.focus = fields.focus;
|
|
415
|
+
return Object.keys(profile).length > 0 ? profile : void 0;
|
|
416
|
+
}
|
|
406
417
|
async function submitApplication(db, chapter, fields, opts) {
|
|
407
418
|
const app = chapter.application;
|
|
408
419
|
for (const f of app.required) {
|
|
@@ -421,6 +432,7 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
421
432
|
}
|
|
422
433
|
if (fields.focus !== void 0) row.focus = fields.focus;
|
|
423
434
|
if (opts.groupId) row.groupId = opts.groupId;
|
|
435
|
+
if (fields.disclaimerAck === true || fields.disclaimerAck === "true") row.disclaimerAckAt = opts.now;
|
|
424
436
|
const { duplicate } = await db.transact(
|
|
425
437
|
[{ t: "update", ns: "applications", id: id2, attrs: row }],
|
|
426
438
|
opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
|
|
@@ -474,6 +486,13 @@ function defineChapter(config) {
|
|
|
474
486
|
if (account !== "invite" && account !== "create" && account !== "none") {
|
|
475
487
|
throw new Error(`defineChapter.account: must be "invite", "create", or "none" \u2014 got "${String(account)}"`);
|
|
476
488
|
}
|
|
489
|
+
const adminNotification = config.sends?.adminNotification ?? "submit";
|
|
490
|
+
if (adminNotification !== "submit" && adminNotification !== "payment" && adminNotification !== "never") {
|
|
491
|
+
throw new Error(
|
|
492
|
+
`defineChapter.sends.adminNotification: must be "submit", "payment", or "never" \u2014 got "${String(adminNotification)}"`
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
const sends = { adminNotification };
|
|
477
496
|
const chapter = {
|
|
478
497
|
config,
|
|
479
498
|
id: id2,
|
|
@@ -487,6 +506,7 @@ function defineChapter(config) {
|
|
|
487
506
|
rules,
|
|
488
507
|
services,
|
|
489
508
|
account,
|
|
509
|
+
sends,
|
|
490
510
|
groupSeed: () => mode === "chapter" ? buildGroupSeed(config) : null
|
|
491
511
|
};
|
|
492
512
|
if (config.url !== void 0) chapter.url = config.url;
|
|
@@ -797,6 +817,20 @@ function clerkUserRequest(input) {
|
|
|
797
817
|
}
|
|
798
818
|
};
|
|
799
819
|
}
|
|
820
|
+
async function refreshUserMetadata(secretKey, email, publicMetadata, fetchImpl) {
|
|
821
|
+
const auth = { authorization: `Bearer ${secretKey}` };
|
|
822
|
+
const found = await fetchImpl(`https://api.clerk.com/v1/users?email_address=${encodeURIComponent(email)}&limit=1`, { headers: auth });
|
|
823
|
+
if (!found.ok) return false;
|
|
824
|
+
const users = await found.json().catch(() => null);
|
|
825
|
+
const id2 = Array.isArray(users) && typeof users[0]?.id === "string" ? users[0].id : void 0;
|
|
826
|
+
if (!id2) return false;
|
|
827
|
+
const patched = await fetchImpl(`https://api.clerk.com/v1/users/${id2}/metadata`, {
|
|
828
|
+
method: "PATCH",
|
|
829
|
+
headers: { ...auth, "content-type": "application/json" },
|
|
830
|
+
body: JSON.stringify({ public_metadata: publicMetadata })
|
|
831
|
+
});
|
|
832
|
+
return patched.ok;
|
|
833
|
+
}
|
|
800
834
|
async function createClerkUser(secretKey, input, fetchImpl = fetch) {
|
|
801
835
|
const { path, body } = clerkUserRequest(input);
|
|
802
836
|
const res = await fetchImpl(`https://api.clerk.com${path}`, {
|
|
@@ -804,7 +838,11 @@ async function createClerkUser(secretKey, input, fetchImpl = fetch) {
|
|
|
804
838
|
headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
|
|
805
839
|
body: JSON.stringify(body)
|
|
806
840
|
});
|
|
807
|
-
|
|
841
|
+
if (res.ok) return { ok: true, status: res.status };
|
|
842
|
+
const healed = heal(res.status);
|
|
843
|
+
if (!healed.existed || !input.publicMetadata) return healed;
|
|
844
|
+
const refreshed = await refreshUserMetadata(secretKey, input.email, input.publicMetadata, fetchImpl).catch(() => false);
|
|
845
|
+
return { ...healed, refreshed };
|
|
808
846
|
}
|
|
809
847
|
|
|
810
848
|
// src/session.ts
|
|
@@ -923,6 +961,12 @@ function isValidTimeZone(tz) {
|
|
|
923
961
|
}
|
|
924
962
|
}
|
|
925
963
|
function resolveScheduling(config) {
|
|
964
|
+
const result = validateScheduling(config);
|
|
965
|
+
if (result.ok) return result.value;
|
|
966
|
+
const detail = Object.entries(result.errors).map(([field, message]) => `${field}: ${message}`).join(" ");
|
|
967
|
+
throw new Error(`scheduling: ${detail}`);
|
|
968
|
+
}
|
|
969
|
+
function validateScheduling(config) {
|
|
926
970
|
const d = config ?? {};
|
|
927
971
|
const c = {
|
|
928
972
|
slotMinutes: d.slotMinutes ?? SCHEDULING_DEFAULTS.slotMinutes,
|
|
@@ -934,20 +978,29 @@ function resolveScheduling(config) {
|
|
|
934
978
|
windowDays: d.windowDays ?? SCHEDULING_DEFAULTS.windowDays,
|
|
935
979
|
summaryTemplate: d.summaryTemplate ?? SCHEDULING_DEFAULTS.summaryTemplate
|
|
936
980
|
};
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
if (!(c.windowDays >= 1 && c.windowDays <= 62))
|
|
942
|
-
|
|
943
|
-
|
|
981
|
+
const errors = {};
|
|
982
|
+
if (!(c.slotMinutes >= 15 && c.slotMinutes <= 240)) {
|
|
983
|
+
errors.slotMinutes = "Slot length must be between 15 and 240 minutes.";
|
|
984
|
+
}
|
|
985
|
+
if (!(c.windowDays >= 1 && c.windowDays <= 62)) {
|
|
986
|
+
errors.windowDays = "Booking window must be between 1 and 62 days (the calendar caps look-ahead at 62).";
|
|
987
|
+
}
|
|
988
|
+
if (!(c.minNoticeHours >= 0 && c.minNoticeHours <= 336)) {
|
|
989
|
+
errors.minNoticeHours = "Minimum notice must be between 0 and 336 hours.";
|
|
990
|
+
}
|
|
991
|
+
if (!(c.startHour >= 0 && c.startHour < c.endHour && c.endHour <= 24)) {
|
|
992
|
+
errors.hours = "Hours must satisfy 0 \u2264 start < end \u2264 24.";
|
|
993
|
+
}
|
|
944
994
|
const days = [...c.days];
|
|
945
|
-
if (!days.length
|
|
946
|
-
|
|
995
|
+
if (!days.length) errors.days = "Pick at least one day.";
|
|
996
|
+
else if (!days.every((n) => Number.isInteger(n) && n >= 0 && n <= 6)) {
|
|
997
|
+
errors.days = "Days must be weekday numbers, 0 (Sunday) through 6 (Saturday).";
|
|
998
|
+
}
|
|
999
|
+
if (typeof c.timezone !== "string" || !isValidTimeZone(c.timezone)) {
|
|
1000
|
+
errors.timezone = `"${String(c.timezone)}" is not a valid IANA timezone (for example "America/Los_Angeles").`;
|
|
947
1001
|
}
|
|
948
|
-
if (typeof c.
|
|
949
|
-
|
|
950
|
-
return { ...c, days };
|
|
1002
|
+
if (typeof c.summaryTemplate !== "string") errors.summaryTemplate = "Calendar summary template must be text.";
|
|
1003
|
+
return Object.keys(errors).length > 0 ? { ok: false, errors } : { ok: true, value: { ...c, days } };
|
|
951
1004
|
}
|
|
952
1005
|
function slotWindow(now, windowDays) {
|
|
953
1006
|
return { from: now, to: now + windowDays * 864e5 };
|
|
@@ -996,6 +1049,7 @@ function applicationBookingUpdate(currentStatus, startAt, htmlLink) {
|
|
|
996
1049
|
}
|
|
997
1050
|
export {
|
|
998
1051
|
SCHEDULING_DEFAULTS,
|
|
1052
|
+
applicantProfile,
|
|
999
1053
|
applicationBookingUpdate,
|
|
1000
1054
|
applicationSummary,
|
|
1001
1055
|
bookingDecision,
|
|
@@ -1052,6 +1106,7 @@ export {
|
|
|
1052
1106
|
stripeForm,
|
|
1053
1107
|
submitApplication,
|
|
1054
1108
|
subscriptionIdempotencyKey,
|
|
1109
|
+
validateScheduling,
|
|
1055
1110
|
verifyStripeSignature,
|
|
1056
1111
|
webhookMutationId
|
|
1057
1112
|
};
|