@odla-ai/chapter 0.10.1 → 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 +48 -8
- package/dist/index.cjs +50 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -8
- package/dist/index.d.ts +63 -8
- package/dist/index.js +50 -9
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +48 -10
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +32 -0
- package/dist/worker/index.d.ts +32 -0
- package/dist/worker/index.js +48 -10
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -175,6 +175,32 @@ interface ChapterApplication {
|
|
|
175
175
|
defaultMaxLen?: number;
|
|
176
176
|
/** Max JSON request body in bytes. Default 32768. */
|
|
177
177
|
bodyCap?: number;
|
|
178
|
+
/** Reject a submit that carries no truthy `disclaimerAck` (400), instead of
|
|
179
|
+
* writing a row with no consent record. Default `false` for back-compat —
|
|
180
|
+
* but turn it on if the disclaimer is a compliance record: a missing ack is
|
|
181
|
+
* otherwise silent, permanent and unreconstructible. Failure is deterministic
|
|
182
|
+
* and surfaces on the first test submit, not intermittently in production. */
|
|
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;
|
|
178
204
|
}
|
|
179
205
|
/** The fully-resolved application config carried on the {@link Chapter}. */
|
|
180
206
|
interface ResolvedApplication {
|
|
@@ -183,6 +209,12 @@ interface ResolvedApplication {
|
|
|
183
209
|
maxLen: Record<string, number>;
|
|
184
210
|
defaultMaxLen: number;
|
|
185
211
|
bodyCap: number;
|
|
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;
|
|
186
218
|
}
|
|
187
219
|
/** The `defineChapter()` config a site fills in. */
|
|
188
220
|
interface ChapterConfig {
|
|
@@ -625,20 +657,35 @@ declare function canceledPatch(): {
|
|
|
625
657
|
|
|
626
658
|
/** Apply defaults + validate the application config. Throws at import on bad shape. */
|
|
627
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;
|
|
666
|
+
/** Whether a submit body carries a genuine disclaimer acknowledgement. Accepts
|
|
667
|
+
* the boolean an API client sends and the string a plain HTML form posts. */
|
|
668
|
+
declare function hasDisclaimerAck(fields: Record<string, unknown>): boolean;
|
|
628
669
|
/**
|
|
629
|
-
* The applicant profile written to the Clerk account's
|
|
630
|
-
*
|
|
631
|
-
* `focus
|
|
632
|
-
*
|
|
633
|
-
*
|
|
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.
|
|
634
676
|
*/
|
|
635
677
|
declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
636
|
-
/** A validated submission, or a 400-worthy validation error the route returns.
|
|
678
|
+
/** A validated submission, or a 400-worthy validation error the route returns.
|
|
679
|
+
* `disclaimerAckAt` reports what THIS request recorded — a number when consent
|
|
680
|
+
* was stamped, `null` when none was supplied. It is always present so a missing
|
|
681
|
+
* consent record is visible in the response rather than silently absent from a
|
|
682
|
+
* row nobody reads until an audit. */
|
|
637
683
|
type SubmitResult = {
|
|
638
684
|
ok: true;
|
|
639
685
|
id: string;
|
|
640
686
|
duplicate: boolean;
|
|
641
687
|
status: string;
|
|
688
|
+
disclaimerAckAt: number | null;
|
|
642
689
|
} | {
|
|
643
690
|
ok: false;
|
|
644
691
|
error: string;
|
|
@@ -707,7 +754,9 @@ interface ProjectionDeps {
|
|
|
707
754
|
declare function projectSharedRecord(deps: ProjectionDeps, person: SharedPerson): Promise<{
|
|
708
755
|
recordId: string;
|
|
709
756
|
}>;
|
|
710
|
-
/** 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. */
|
|
711
760
|
interface Applicant {
|
|
712
761
|
applicationId: string;
|
|
713
762
|
email: string;
|
|
@@ -715,12 +764,18 @@ interface Applicant {
|
|
|
715
764
|
lastName?: string;
|
|
716
765
|
phone?: string;
|
|
717
766
|
linkedin?: string;
|
|
767
|
+
extra?: Record<string, unknown>;
|
|
718
768
|
}
|
|
719
769
|
/**
|
|
720
770
|
* Project an arriving applicant into this chapter's `crm_record`, so a new
|
|
721
771
|
* application shows up in the CRM immediately, unified by email with any prior
|
|
722
772
|
* record. Idempotent per application (`apply:${applicationId}`). Best-effort at
|
|
723
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.
|
|
724
779
|
*/
|
|
725
780
|
declare function projectApplicant(deps: ProjectionDeps, applicant: Applicant): Promise<{
|
|
726
781
|
recordId: string;
|
|
@@ -1016,4 +1071,4 @@ type ApplicationBookingPatch = {
|
|
|
1016
1071
|
* already there (never backward). */
|
|
1017
1072
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
1018
1073
|
|
|
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 };
|
|
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
|
@@ -175,6 +175,32 @@ interface ChapterApplication {
|
|
|
175
175
|
defaultMaxLen?: number;
|
|
176
176
|
/** Max JSON request body in bytes. Default 32768. */
|
|
177
177
|
bodyCap?: number;
|
|
178
|
+
/** Reject a submit that carries no truthy `disclaimerAck` (400), instead of
|
|
179
|
+
* writing a row with no consent record. Default `false` for back-compat —
|
|
180
|
+
* but turn it on if the disclaimer is a compliance record: a missing ack is
|
|
181
|
+
* otherwise silent, permanent and unreconstructible. Failure is deterministic
|
|
182
|
+
* and surfaces on the first test submit, not intermittently in production. */
|
|
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;
|
|
178
204
|
}
|
|
179
205
|
/** The fully-resolved application config carried on the {@link Chapter}. */
|
|
180
206
|
interface ResolvedApplication {
|
|
@@ -183,6 +209,12 @@ interface ResolvedApplication {
|
|
|
183
209
|
maxLen: Record<string, number>;
|
|
184
210
|
defaultMaxLen: number;
|
|
185
211
|
bodyCap: number;
|
|
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;
|
|
186
218
|
}
|
|
187
219
|
/** The `defineChapter()` config a site fills in. */
|
|
188
220
|
interface ChapterConfig {
|
|
@@ -625,20 +657,35 @@ declare function canceledPatch(): {
|
|
|
625
657
|
|
|
626
658
|
/** Apply defaults + validate the application config. Throws at import on bad shape. */
|
|
627
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;
|
|
666
|
+
/** Whether a submit body carries a genuine disclaimer acknowledgement. Accepts
|
|
667
|
+
* the boolean an API client sends and the string a plain HTML form posts. */
|
|
668
|
+
declare function hasDisclaimerAck(fields: Record<string, unknown>): boolean;
|
|
628
669
|
/**
|
|
629
|
-
* The applicant profile written to the Clerk account's
|
|
630
|
-
*
|
|
631
|
-
* `focus
|
|
632
|
-
*
|
|
633
|
-
*
|
|
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.
|
|
634
676
|
*/
|
|
635
677
|
declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
636
|
-
/** A validated submission, or a 400-worthy validation error the route returns.
|
|
678
|
+
/** A validated submission, or a 400-worthy validation error the route returns.
|
|
679
|
+
* `disclaimerAckAt` reports what THIS request recorded — a number when consent
|
|
680
|
+
* was stamped, `null` when none was supplied. It is always present so a missing
|
|
681
|
+
* consent record is visible in the response rather than silently absent from a
|
|
682
|
+
* row nobody reads until an audit. */
|
|
637
683
|
type SubmitResult = {
|
|
638
684
|
ok: true;
|
|
639
685
|
id: string;
|
|
640
686
|
duplicate: boolean;
|
|
641
687
|
status: string;
|
|
688
|
+
disclaimerAckAt: number | null;
|
|
642
689
|
} | {
|
|
643
690
|
ok: false;
|
|
644
691
|
error: string;
|
|
@@ -707,7 +754,9 @@ interface ProjectionDeps {
|
|
|
707
754
|
declare function projectSharedRecord(deps: ProjectionDeps, person: SharedPerson): Promise<{
|
|
708
755
|
recordId: string;
|
|
709
756
|
}>;
|
|
710
|
-
/** 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. */
|
|
711
760
|
interface Applicant {
|
|
712
761
|
applicationId: string;
|
|
713
762
|
email: string;
|
|
@@ -715,12 +764,18 @@ interface Applicant {
|
|
|
715
764
|
lastName?: string;
|
|
716
765
|
phone?: string;
|
|
717
766
|
linkedin?: string;
|
|
767
|
+
extra?: Record<string, unknown>;
|
|
718
768
|
}
|
|
719
769
|
/**
|
|
720
770
|
* Project an arriving applicant into this chapter's `crm_record`, so a new
|
|
721
771
|
* application shows up in the CRM immediately, unified by email with any prior
|
|
722
772
|
* record. Idempotent per application (`apply:${applicationId}`). Best-effort at
|
|
723
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.
|
|
724
779
|
*/
|
|
725
780
|
declare function projectApplicant(deps: ProjectionDeps, applicant: Applicant): Promise<{
|
|
726
781
|
recordId: string;
|
|
@@ -1016,4 +1071,4 @@ type ApplicationBookingPatch = {
|
|
|
1016
1071
|
* already there (never backward). */
|
|
1017
1072
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
1018
1073
|
|
|
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 };
|
|
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,23 +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
|
-
bodyCap: a?.bodyCap ?? 32768
|
|
409
|
+
bodyCap: a?.bodyCap ?? 32768,
|
|
410
|
+
requireDisclaimerAck: a?.requireDisclaimerAck ?? false,
|
|
411
|
+
profileFields: a?.profileFields ?? null,
|
|
412
|
+
crmFields: a?.crmFields ?? [],
|
|
413
|
+
maxArrayLen: a?.maxArrayLen ?? 100,
|
|
414
|
+
validateEmail: a?.validateEmail ?? true
|
|
404
415
|
};
|
|
405
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
|
+
}
|
|
425
|
+
function hasDisclaimerAck(fields) {
|
|
426
|
+
return fields.disclaimerAck === true || fields.disclaimerAck === "true";
|
|
427
|
+
}
|
|
406
428
|
var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
|
|
407
429
|
function applicantProfile(chapter, fields) {
|
|
430
|
+
const app = chapter.application;
|
|
431
|
+
const allowed = (f) => app.profileFields === null || app.profileFields.includes(f);
|
|
408
432
|
const profile = {};
|
|
409
|
-
for (const f of [...
|
|
410
|
-
if (IDENTITY_FIELDS.has(f)) continue;
|
|
433
|
+
for (const f of [...app.required, ...app.optional]) {
|
|
434
|
+
if (IDENTITY_FIELDS.has(f) || !allowed(f)) continue;
|
|
411
435
|
const v = fields[f];
|
|
412
436
|
if (typeof v === "string" && v.trim() !== "") profile[f] = v.trim();
|
|
413
437
|
}
|
|
414
|
-
if (fields.focus !== void 0) profile.focus = fields.focus;
|
|
438
|
+
if (fields.focus !== void 0 && allowed("focus")) profile.focus = clampArray(fields.focus, app.maxArrayLen);
|
|
415
439
|
return Object.keys(profile).length > 0 ? profile : void 0;
|
|
416
440
|
}
|
|
417
441
|
async function submitApplication(db, chapter, fields, opts) {
|
|
@@ -425,19 +449,26 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
425
449
|
const cap = app.maxLen[f] ?? app.defaultMaxLen;
|
|
426
450
|
if (typeof v === "string" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };
|
|
427
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
|
+
}
|
|
455
|
+
const acked = hasDisclaimerAck(fields);
|
|
456
|
+
if (app.requireDisclaimerAck && !acked) {
|
|
457
|
+
return { ok: false, error: "disclaimerAck is required" };
|
|
458
|
+
}
|
|
428
459
|
const id2 = opts.newId();
|
|
429
460
|
const row = { id: id2, status: chapter.pipeline.initial, createdAt: opts.now };
|
|
430
461
|
for (const f of [...app.required, ...app.optional]) {
|
|
431
462
|
if (typeof fields[f] === "string") row[f] = fields[f].trim();
|
|
432
463
|
}
|
|
433
|
-
if (fields.focus !== void 0) row.focus = fields.focus;
|
|
464
|
+
if (fields.focus !== void 0) row.focus = clampArray(fields.focus, app.maxArrayLen);
|
|
434
465
|
if (opts.groupId) row.groupId = opts.groupId;
|
|
435
|
-
if (
|
|
466
|
+
if (acked) row.disclaimerAckAt = opts.now;
|
|
436
467
|
const { duplicate } = await db.transact(
|
|
437
468
|
[{ t: "update", ns: "applications", id: id2, attrs: row }],
|
|
438
469
|
opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
|
|
439
470
|
);
|
|
440
|
-
return { ok: true, id: id2, duplicate, status: chapter.pipeline.initial };
|
|
471
|
+
return { ok: true, id: id2, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
|
|
441
472
|
}
|
|
442
473
|
function joinConfig(group, paymentsReady2) {
|
|
443
474
|
return {
|
|
@@ -772,7 +803,7 @@ async function projectSharedRecord(deps, person) {
|
|
|
772
803
|
return upsertPerson(deps, { email: person.email, input: sharedPersonInput(person), mutationId: `share:${person.hubRecordId}` });
|
|
773
804
|
}
|
|
774
805
|
async function projectApplicant(deps, applicant) {
|
|
775
|
-
const
|
|
806
|
+
const base = sharedPersonInput({
|
|
776
807
|
email: applicant.email,
|
|
777
808
|
firstName: applicant.firstName,
|
|
778
809
|
lastName: applicant.lastName,
|
|
@@ -780,7 +811,14 @@ async function projectApplicant(deps, applicant) {
|
|
|
780
811
|
linkedin: applicant.linkedin,
|
|
781
812
|
hubRecordId: applicant.applicationId
|
|
782
813
|
});
|
|
783
|
-
|
|
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
|
+
}
|
|
784
822
|
}
|
|
785
823
|
|
|
786
824
|
// src/clerk.ts
|
|
@@ -1061,6 +1099,7 @@ export {
|
|
|
1061
1099
|
canTransition,
|
|
1062
1100
|
canceledPatch,
|
|
1063
1101
|
chapterDb,
|
|
1102
|
+
clampArray,
|
|
1064
1103
|
clerkInviteRequest,
|
|
1065
1104
|
clerkUserRequest,
|
|
1066
1105
|
createChapterIntegration,
|
|
@@ -1073,11 +1112,13 @@ export {
|
|
|
1073
1112
|
findApplicationRef,
|
|
1074
1113
|
firstPaymentPatch,
|
|
1075
1114
|
getVaultSecret,
|
|
1115
|
+
hasDisclaimerAck,
|
|
1076
1116
|
introIdempotencyKey,
|
|
1077
1117
|
isAdminRole,
|
|
1078
1118
|
isAlreadySent,
|
|
1079
1119
|
isReconcilable,
|
|
1080
1120
|
isSlotAvailable,
|
|
1121
|
+
isValidEmail,
|
|
1081
1122
|
joinConfig,
|
|
1082
1123
|
meetingCreateRow,
|
|
1083
1124
|
meetingRescheduleUpdate,
|