@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.
- package/README.md +27 -5
- package/dist/index.cjs +39 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -7
- package/dist/index.d.ts +47 -7
- package/dist/index.js +39 -7
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +31 -7
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +25 -0
- package/dist/worker/index.d.ts +25 -0
- package/dist/worker/index.js +31 -7
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -181,6 +181,26 @@ interface ChapterApplication {
|
|
|
181
181
|
* otherwise silent, permanent and unreconstructible. Failure is deterministic
|
|
182
182
|
* and surfaces on the first test submit, not intermittently in production. */
|
|
183
183
|
requireDisclaimerAck?: boolean;
|
|
184
|
+
/** Allowlist of fields that reach the Clerk account's client-readable
|
|
185
|
+
* `public_metadata.profile`. Default (unset) projects every non-identity
|
|
186
|
+
* configured field — convenient, but it also exposes free-text and
|
|
187
|
+
* third-party fields (`message`, `referral`). Set this to a curated list
|
|
188
|
+
* (e.g. `["phone", "state", "focus"]`) to keep confidential fields db-only.
|
|
189
|
+
* Expected to become required-in-spirit at 1.0. */
|
|
190
|
+
profileFields?: readonly string[];
|
|
191
|
+
/** Extra application fields carried into the one-way CRM projection, on top of
|
|
192
|
+
* the built-in identity/contact set. Each MUST be declared on your crm person
|
|
193
|
+
* type or the enrichment is dropped (the base person still projects). Default
|
|
194
|
+
* none. */
|
|
195
|
+
crmFields?: readonly string[];
|
|
196
|
+
/** Cap on the element count of array-valued fields (e.g. `focus`), so a client
|
|
197
|
+
* cannot post a 10k-element array into a row or into Clerk metadata.
|
|
198
|
+
* Non-primitive elements are dropped. Default 100. */
|
|
199
|
+
maxArrayLen?: number;
|
|
200
|
+
/** Validate that a field literally named `email` looks like an email address,
|
|
201
|
+
* returning a 400 rather than accepting input the downstream Clerk create will
|
|
202
|
+
* reject anyway. Default `true`; a valid application is never newly rejected. */
|
|
203
|
+
validateEmail?: boolean;
|
|
184
204
|
}
|
|
185
205
|
/** The fully-resolved application config carried on the {@link Chapter}. */
|
|
186
206
|
interface ResolvedApplication {
|
|
@@ -190,6 +210,11 @@ interface ResolvedApplication {
|
|
|
190
210
|
defaultMaxLen: number;
|
|
191
211
|
bodyCap: number;
|
|
192
212
|
requireDisclaimerAck: boolean;
|
|
213
|
+
/** Resolved Clerk-metadata allowlist; `null` means "all non-identity fields". */
|
|
214
|
+
profileFields: readonly string[] | null;
|
|
215
|
+
crmFields: readonly string[];
|
|
216
|
+
maxArrayLen: number;
|
|
217
|
+
validateEmail: boolean;
|
|
193
218
|
}
|
|
194
219
|
/** The `defineChapter()` config a site fills in. */
|
|
195
220
|
interface ChapterConfig {
|
|
@@ -632,15 +657,22 @@ declare function canceledPatch(): {
|
|
|
632
657
|
|
|
633
658
|
/** Apply defaults + validate the application config. Throws at import on bad shape. */
|
|
634
659
|
declare function resolveApplication(a: ChapterApplication | undefined): ResolvedApplication;
|
|
660
|
+
/** Whether a string looks like an email address (see {@link EMAIL_RE}). Exported
|
|
661
|
+
* so a site building its own submit path applies the same rule chapter does. */
|
|
662
|
+
declare function isValidEmail(value: unknown): boolean;
|
|
663
|
+
/** Bound an array-valued field: drop non-primitive elements and cap the length,
|
|
664
|
+
* so a client cannot post an unbounded array. Non-arrays pass through unchanged. */
|
|
665
|
+
declare function clampArray(value: unknown, max: number): unknown;
|
|
635
666
|
/** Whether a submit body carries a genuine disclaimer acknowledgement. Accepts
|
|
636
667
|
* the boolean an API client sends and the string a plain HTML form posts. */
|
|
637
668
|
declare function hasDisclaimerAck(fields: Record<string, unknown>): boolean;
|
|
638
669
|
/**
|
|
639
|
-
* The applicant profile written to the Clerk account's
|
|
640
|
-
*
|
|
641
|
-
* `focus
|
|
642
|
-
*
|
|
643
|
-
*
|
|
670
|
+
* The applicant profile written to the Clerk account's client-readable
|
|
671
|
+
* `public_metadata.profile`. Projects each configured non-identity field, plus
|
|
672
|
+
* `focus` (clamped) — but ONLY those in `application.profileFields` when that
|
|
673
|
+
* allowlist is set, so a site keeps confidential fields (`message`, `referral`)
|
|
674
|
+
* db-only. Derived from config, so a site's own field names project without this
|
|
675
|
+
* package knowing them. Pure; returns `undefined` when there is nothing to write.
|
|
644
676
|
*/
|
|
645
677
|
declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
646
678
|
/** A validated submission, or a 400-worthy validation error the route returns.
|
|
@@ -722,7 +754,9 @@ interface ProjectionDeps {
|
|
|
722
754
|
declare function projectSharedRecord(deps: ProjectionDeps, person: SharedPerson): Promise<{
|
|
723
755
|
recordId: string;
|
|
724
756
|
}>;
|
|
725
|
-
/** An arriving applicant, as far as the CRM projection cares.
|
|
757
|
+
/** An arriving applicant, as far as the CRM projection cares. `extra` carries the
|
|
758
|
+
* site-configured `crmFields` (values from the application), merged on top of the
|
|
759
|
+
* built-in identity/contact set. */
|
|
726
760
|
interface Applicant {
|
|
727
761
|
applicationId: string;
|
|
728
762
|
email: string;
|
|
@@ -730,12 +764,18 @@ interface Applicant {
|
|
|
730
764
|
lastName?: string;
|
|
731
765
|
phone?: string;
|
|
732
766
|
linkedin?: string;
|
|
767
|
+
extra?: Record<string, unknown>;
|
|
733
768
|
}
|
|
734
769
|
/**
|
|
735
770
|
* Project an arriving applicant into this chapter's `crm_record`, so a new
|
|
736
771
|
* application shows up in the CRM immediately, unified by email with any prior
|
|
737
772
|
* record. Idempotent per application (`apply:${applicationId}`). Best-effort at
|
|
738
773
|
* the call site — a projection failure never fails the application.
|
|
774
|
+
*
|
|
775
|
+
* `extra` fields (a site's `crmFields`) are merged on top of the base person. If
|
|
776
|
+
* an extra field is not on the crm person type, crm validation throws — so the
|
|
777
|
+
* projection retries with the base person alone, ensuring a misconfigured
|
|
778
|
+
* enrichment never silently drops the applicant from the CRM entirely.
|
|
739
779
|
*/
|
|
740
780
|
declare function projectApplicant(deps: ProjectionDeps, applicant: Applicant): Promise<{
|
|
741
781
|
recordId: string;
|
|
@@ -1031,4 +1071,4 @@ type ApplicationBookingPatch = {
|
|
|
1031
1071
|
* already there (never backward). */
|
|
1032
1072
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
1033
1073
|
|
|
1034
|
-
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, hasDisclaimerAck, 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 };
|
|
1074
|
+
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, clampArray, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, 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
|
@@ -181,6 +181,26 @@ interface ChapterApplication {
|
|
|
181
181
|
* otherwise silent, permanent and unreconstructible. Failure is deterministic
|
|
182
182
|
* and surfaces on the first test submit, not intermittently in production. */
|
|
183
183
|
requireDisclaimerAck?: boolean;
|
|
184
|
+
/** Allowlist of fields that reach the Clerk account's client-readable
|
|
185
|
+
* `public_metadata.profile`. Default (unset) projects every non-identity
|
|
186
|
+
* configured field — convenient, but it also exposes free-text and
|
|
187
|
+
* third-party fields (`message`, `referral`). Set this to a curated list
|
|
188
|
+
* (e.g. `["phone", "state", "focus"]`) to keep confidential fields db-only.
|
|
189
|
+
* Expected to become required-in-spirit at 1.0. */
|
|
190
|
+
profileFields?: readonly string[];
|
|
191
|
+
/** Extra application fields carried into the one-way CRM projection, on top of
|
|
192
|
+
* the built-in identity/contact set. Each MUST be declared on your crm person
|
|
193
|
+
* type or the enrichment is dropped (the base person still projects). Default
|
|
194
|
+
* none. */
|
|
195
|
+
crmFields?: readonly string[];
|
|
196
|
+
/** Cap on the element count of array-valued fields (e.g. `focus`), so a client
|
|
197
|
+
* cannot post a 10k-element array into a row or into Clerk metadata.
|
|
198
|
+
* Non-primitive elements are dropped. Default 100. */
|
|
199
|
+
maxArrayLen?: number;
|
|
200
|
+
/** Validate that a field literally named `email` looks like an email address,
|
|
201
|
+
* returning a 400 rather than accepting input the downstream Clerk create will
|
|
202
|
+
* reject anyway. Default `true`; a valid application is never newly rejected. */
|
|
203
|
+
validateEmail?: boolean;
|
|
184
204
|
}
|
|
185
205
|
/** The fully-resolved application config carried on the {@link Chapter}. */
|
|
186
206
|
interface ResolvedApplication {
|
|
@@ -190,6 +210,11 @@ interface ResolvedApplication {
|
|
|
190
210
|
defaultMaxLen: number;
|
|
191
211
|
bodyCap: number;
|
|
192
212
|
requireDisclaimerAck: boolean;
|
|
213
|
+
/** Resolved Clerk-metadata allowlist; `null` means "all non-identity fields". */
|
|
214
|
+
profileFields: readonly string[] | null;
|
|
215
|
+
crmFields: readonly string[];
|
|
216
|
+
maxArrayLen: number;
|
|
217
|
+
validateEmail: boolean;
|
|
193
218
|
}
|
|
194
219
|
/** The `defineChapter()` config a site fills in. */
|
|
195
220
|
interface ChapterConfig {
|
|
@@ -632,15 +657,22 @@ declare function canceledPatch(): {
|
|
|
632
657
|
|
|
633
658
|
/** Apply defaults + validate the application config. Throws at import on bad shape. */
|
|
634
659
|
declare function resolveApplication(a: ChapterApplication | undefined): ResolvedApplication;
|
|
660
|
+
/** Whether a string looks like an email address (see {@link EMAIL_RE}). Exported
|
|
661
|
+
* so a site building its own submit path applies the same rule chapter does. */
|
|
662
|
+
declare function isValidEmail(value: unknown): boolean;
|
|
663
|
+
/** Bound an array-valued field: drop non-primitive elements and cap the length,
|
|
664
|
+
* so a client cannot post an unbounded array. Non-arrays pass through unchanged. */
|
|
665
|
+
declare function clampArray(value: unknown, max: number): unknown;
|
|
635
666
|
/** Whether a submit body carries a genuine disclaimer acknowledgement. Accepts
|
|
636
667
|
* the boolean an API client sends and the string a plain HTML form posts. */
|
|
637
668
|
declare function hasDisclaimerAck(fields: Record<string, unknown>): boolean;
|
|
638
669
|
/**
|
|
639
|
-
* The applicant profile written to the Clerk account's
|
|
640
|
-
*
|
|
641
|
-
* `focus
|
|
642
|
-
*
|
|
643
|
-
*
|
|
670
|
+
* The applicant profile written to the Clerk account's client-readable
|
|
671
|
+
* `public_metadata.profile`. Projects each configured non-identity field, plus
|
|
672
|
+
* `focus` (clamped) — but ONLY those in `application.profileFields` when that
|
|
673
|
+
* allowlist is set, so a site keeps confidential fields (`message`, `referral`)
|
|
674
|
+
* db-only. Derived from config, so a site's own field names project without this
|
|
675
|
+
* package knowing them. Pure; returns `undefined` when there is nothing to write.
|
|
644
676
|
*/
|
|
645
677
|
declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
646
678
|
/** A validated submission, or a 400-worthy validation error the route returns.
|
|
@@ -722,7 +754,9 @@ interface ProjectionDeps {
|
|
|
722
754
|
declare function projectSharedRecord(deps: ProjectionDeps, person: SharedPerson): Promise<{
|
|
723
755
|
recordId: string;
|
|
724
756
|
}>;
|
|
725
|
-
/** An arriving applicant, as far as the CRM projection cares.
|
|
757
|
+
/** An arriving applicant, as far as the CRM projection cares. `extra` carries the
|
|
758
|
+
* site-configured `crmFields` (values from the application), merged on top of the
|
|
759
|
+
* built-in identity/contact set. */
|
|
726
760
|
interface Applicant {
|
|
727
761
|
applicationId: string;
|
|
728
762
|
email: string;
|
|
@@ -730,12 +764,18 @@ interface Applicant {
|
|
|
730
764
|
lastName?: string;
|
|
731
765
|
phone?: string;
|
|
732
766
|
linkedin?: string;
|
|
767
|
+
extra?: Record<string, unknown>;
|
|
733
768
|
}
|
|
734
769
|
/**
|
|
735
770
|
* Project an arriving applicant into this chapter's `crm_record`, so a new
|
|
736
771
|
* application shows up in the CRM immediately, unified by email with any prior
|
|
737
772
|
* record. Idempotent per application (`apply:${applicationId}`). Best-effort at
|
|
738
773
|
* the call site — a projection failure never fails the application.
|
|
774
|
+
*
|
|
775
|
+
* `extra` fields (a site's `crmFields`) are merged on top of the base person. If
|
|
776
|
+
* an extra field is not on the crm person type, crm validation throws — so the
|
|
777
|
+
* projection retries with the base person alone, ensuring a misconfigured
|
|
778
|
+
* enrichment never silently drops the applicant from the CRM entirely.
|
|
739
779
|
*/
|
|
740
780
|
declare function projectApplicant(deps: ProjectionDeps, applicant: Applicant): Promise<{
|
|
741
781
|
recordId: string;
|
|
@@ -1031,4 +1071,4 @@ type ApplicationBookingPatch = {
|
|
|
1031
1071
|
* already there (never backward). */
|
|
1032
1072
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
1033
1073
|
|
|
1034
|
-
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, hasDisclaimerAck, 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 };
|
|
1074
|
+
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, clampArray, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, 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
|
@@ -395,27 +395,47 @@ function resolveApplication(a) {
|
|
|
395
395
|
throw new Error(`defineChapter.application.${name}: must be an array of field-name strings`);
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
|
+
if (a?.profileFields !== void 0 && (!Array.isArray(a.profileFields) || !a.profileFields.every((f) => typeof f === "string" && f !== ""))) {
|
|
399
|
+
throw new Error("defineChapter.application.profileFields: must be an array of field-name strings");
|
|
400
|
+
}
|
|
401
|
+
if (a?.crmFields !== void 0 && (!Array.isArray(a.crmFields) || !a.crmFields.every((f) => typeof f === "string" && f !== ""))) {
|
|
402
|
+
throw new Error("defineChapter.application.crmFields: must be an array of field-name strings");
|
|
403
|
+
}
|
|
398
404
|
return {
|
|
399
405
|
required,
|
|
400
406
|
optional,
|
|
401
407
|
maxLen: a?.maxLen ?? {},
|
|
402
408
|
defaultMaxLen: a?.defaultMaxLen ?? 2e3,
|
|
403
409
|
bodyCap: a?.bodyCap ?? 32768,
|
|
404
|
-
requireDisclaimerAck: a?.requireDisclaimerAck ?? false
|
|
410
|
+
requireDisclaimerAck: a?.requireDisclaimerAck ?? false,
|
|
411
|
+
profileFields: a?.profileFields ?? null,
|
|
412
|
+
crmFields: a?.crmFields ?? [],
|
|
413
|
+
maxArrayLen: a?.maxArrayLen ?? 100,
|
|
414
|
+
validateEmail: a?.validateEmail ?? true
|
|
405
415
|
};
|
|
406
416
|
}
|
|
417
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
418
|
+
function isValidEmail(value) {
|
|
419
|
+
return typeof value === "string" && EMAIL_RE.test(value);
|
|
420
|
+
}
|
|
421
|
+
function clampArray(value, max) {
|
|
422
|
+
if (!Array.isArray(value)) return value;
|
|
423
|
+
return value.filter((x) => typeof x === "string" || typeof x === "number" || typeof x === "boolean").slice(0, max);
|
|
424
|
+
}
|
|
407
425
|
function hasDisclaimerAck(fields) {
|
|
408
426
|
return fields.disclaimerAck === true || fields.disclaimerAck === "true";
|
|
409
427
|
}
|
|
410
428
|
var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
|
|
411
429
|
function applicantProfile(chapter, fields) {
|
|
430
|
+
const app = chapter.application;
|
|
431
|
+
const allowed = (f) => app.profileFields === null || app.profileFields.includes(f);
|
|
412
432
|
const profile = {};
|
|
413
|
-
for (const f of [...
|
|
414
|
-
if (IDENTITY_FIELDS.has(f)) continue;
|
|
433
|
+
for (const f of [...app.required, ...app.optional]) {
|
|
434
|
+
if (IDENTITY_FIELDS.has(f) || !allowed(f)) continue;
|
|
415
435
|
const v = fields[f];
|
|
416
436
|
if (typeof v === "string" && v.trim() !== "") profile[f] = v.trim();
|
|
417
437
|
}
|
|
418
|
-
if (fields.focus !== void 0) profile.focus = fields.focus;
|
|
438
|
+
if (fields.focus !== void 0 && allowed("focus")) profile.focus = clampArray(fields.focus, app.maxArrayLen);
|
|
419
439
|
return Object.keys(profile).length > 0 ? profile : void 0;
|
|
420
440
|
}
|
|
421
441
|
async function submitApplication(db, chapter, fields, opts) {
|
|
@@ -429,6 +449,9 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
429
449
|
const cap = app.maxLen[f] ?? app.defaultMaxLen;
|
|
430
450
|
if (typeof v === "string" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };
|
|
431
451
|
}
|
|
452
|
+
if (app.validateEmail && typeof fields.email === "string" && !isValidEmail(fields.email)) {
|
|
453
|
+
return { ok: false, error: "email must be a valid email address" };
|
|
454
|
+
}
|
|
432
455
|
const acked = hasDisclaimerAck(fields);
|
|
433
456
|
if (app.requireDisclaimerAck && !acked) {
|
|
434
457
|
return { ok: false, error: "disclaimerAck is required" };
|
|
@@ -438,7 +461,7 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
438
461
|
for (const f of [...app.required, ...app.optional]) {
|
|
439
462
|
if (typeof fields[f] === "string") row[f] = fields[f].trim();
|
|
440
463
|
}
|
|
441
|
-
if (fields.focus !== void 0) row.focus = fields.focus;
|
|
464
|
+
if (fields.focus !== void 0) row.focus = clampArray(fields.focus, app.maxArrayLen);
|
|
442
465
|
if (opts.groupId) row.groupId = opts.groupId;
|
|
443
466
|
if (acked) row.disclaimerAckAt = opts.now;
|
|
444
467
|
const { duplicate } = await db.transact(
|
|
@@ -780,7 +803,7 @@ async function projectSharedRecord(deps, person) {
|
|
|
780
803
|
return upsertPerson(deps, { email: person.email, input: sharedPersonInput(person), mutationId: `share:${person.hubRecordId}` });
|
|
781
804
|
}
|
|
782
805
|
async function projectApplicant(deps, applicant) {
|
|
783
|
-
const
|
|
806
|
+
const base = sharedPersonInput({
|
|
784
807
|
email: applicant.email,
|
|
785
808
|
firstName: applicant.firstName,
|
|
786
809
|
lastName: applicant.lastName,
|
|
@@ -788,7 +811,14 @@ async function projectApplicant(deps, applicant) {
|
|
|
788
811
|
linkedin: applicant.linkedin,
|
|
789
812
|
hubRecordId: applicant.applicationId
|
|
790
813
|
});
|
|
791
|
-
|
|
814
|
+
const mutationId = `apply:${applicant.applicationId}`;
|
|
815
|
+
const extra = applicant.extra ?? {};
|
|
816
|
+
if (Object.keys(extra).length === 0) return upsertPerson(deps, { email: applicant.email, input: base, mutationId });
|
|
817
|
+
try {
|
|
818
|
+
return await upsertPerson(deps, { email: applicant.email, input: { ...base, ...extra }, mutationId });
|
|
819
|
+
} catch {
|
|
820
|
+
return upsertPerson(deps, { email: applicant.email, input: base, mutationId });
|
|
821
|
+
}
|
|
792
822
|
}
|
|
793
823
|
|
|
794
824
|
// src/clerk.ts
|
|
@@ -1069,6 +1099,7 @@ export {
|
|
|
1069
1099
|
canTransition,
|
|
1070
1100
|
canceledPatch,
|
|
1071
1101
|
chapterDb,
|
|
1102
|
+
clampArray,
|
|
1072
1103
|
clerkInviteRequest,
|
|
1073
1104
|
clerkUserRequest,
|
|
1074
1105
|
createChapterIntegration,
|
|
@@ -1087,6 +1118,7 @@ export {
|
|
|
1087
1118
|
isAlreadySent,
|
|
1088
1119
|
isReconcilable,
|
|
1089
1120
|
isSlotAvailable,
|
|
1121
|
+
isValidEmail,
|
|
1090
1122
|
joinConfig,
|
|
1091
1123
|
meetingCreateRow,
|
|
1092
1124
|
meetingRescheduleUpdate,
|