@odla-ai/chapter 0.5.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -28
- package/dist/index.cjs +175 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +157 -9
- package/dist/index.d.ts +157 -9
- package/dist/index.js +175 -17
- package/dist/index.js.map +1 -1
- package/dist/ui/index.d.ts +5 -1
- package/dist/ui/index.js +3 -2
- package/dist/ui/index.js.map +1 -1
- package/dist/worker/index.cjs +369 -17
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +69 -6
- package/dist/worker/index.d.ts +69 -6
- package/dist/worker/index.js +369 -17
- package/dist/worker/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -210,7 +210,14 @@ interface ChapterConfig {
|
|
|
210
210
|
auth?: ChapterAuth;
|
|
211
211
|
/** odla services (db implied). Default `["db","calendar","o11y"]`. */
|
|
212
212
|
services?: readonly string[];
|
|
213
|
-
|
|
213
|
+
/** Apply-time account provisioning: `"invite"` (default) mints a Clerk
|
|
214
|
+
* invitation, `"create"` makes the account server-side (so join can say the
|
|
215
|
+
* account is ready), `"none"` skips it. Any of these needs a `clerk_secret_key`
|
|
216
|
+
* vault secret to act. */
|
|
217
|
+
account?: AccountModel;
|
|
218
|
+
}
|
|
219
|
+
/** Apply-time Clerk account provisioning model. */
|
|
220
|
+
type AccountModel = "invite" | "create" | "none";
|
|
214
221
|
/** The resolved engine `defineChapter()` returns. */
|
|
215
222
|
interface Chapter {
|
|
216
223
|
config: ChapterConfig;
|
|
@@ -230,6 +237,8 @@ interface Chapter {
|
|
|
230
237
|
schema: DbSchema;
|
|
231
238
|
rules: DbRules;
|
|
232
239
|
services: readonly string[];
|
|
240
|
+
/** Resolved apply-time account provisioning model (default `"invite"`). */
|
|
241
|
+
account: AccountModel;
|
|
233
242
|
/** The seed `groups` row derived from config (chapter mode), else `null`. */
|
|
234
243
|
groupSeed(): Record<string, unknown> | null;
|
|
235
244
|
}
|
|
@@ -433,6 +442,54 @@ declare function planDelivery(input: {
|
|
|
433
442
|
force?: boolean;
|
|
434
443
|
}): DeliveryDecision;
|
|
435
444
|
|
|
445
|
+
/** The mail transport (the worker's Cloudflare SEND_EMAIL binding). */
|
|
446
|
+
interface MailSender {
|
|
447
|
+
send(payload: {
|
|
448
|
+
from: string;
|
|
449
|
+
to: string[];
|
|
450
|
+
subject: string;
|
|
451
|
+
text?: string;
|
|
452
|
+
replyTo?: string;
|
|
453
|
+
}): Promise<{
|
|
454
|
+
messageId: string;
|
|
455
|
+
}>;
|
|
456
|
+
}
|
|
457
|
+
/** Deps for {@link sendTemplated} — the db, the env name (drives the fail-safe),
|
|
458
|
+
* the transport + from (absent ⇒ log-only), and injected clock/id. */
|
|
459
|
+
interface NotifyDeps {
|
|
460
|
+
db: ChapterDb;
|
|
461
|
+
envName: string;
|
|
462
|
+
sender?: MailSender;
|
|
463
|
+
from?: string;
|
|
464
|
+
now: () => number;
|
|
465
|
+
newId: () => string;
|
|
466
|
+
}
|
|
467
|
+
/** One templated send. `dedupeKey` is the exactly-once key. */
|
|
468
|
+
interface NotifyInput {
|
|
469
|
+
group: EmailGroup;
|
|
470
|
+
template: string;
|
|
471
|
+
to: string;
|
|
472
|
+
vars: Record<string, string>;
|
|
473
|
+
dedupeKey: string;
|
|
474
|
+
applicationId?: string;
|
|
475
|
+
/** The admin test route may send a disabled template. */
|
|
476
|
+
force?: boolean;
|
|
477
|
+
}
|
|
478
|
+
/** The outcome. `sent:true` includes the already-sent short-circuit. */
|
|
479
|
+
interface NotifyResult {
|
|
480
|
+
sent: boolean;
|
|
481
|
+
reason?: string;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Send a templated lifecycle email exactly once. Short-circuits on a prior
|
|
485
|
+
* successful send; otherwise plans delivery (dev fail-safe applies), sends via the
|
|
486
|
+
* transport when one is wired and the plan calls for it, and records an emailLog
|
|
487
|
+
* row either way (success keyed for exactly-once, failure unkeyed for retry).
|
|
488
|
+
*/
|
|
489
|
+
declare function sendTemplated(deps: NotifyDeps, input: NotifyInput): Promise<NotifyResult>;
|
|
490
|
+
/** Project a `groups` row into the {@link EmailGroup} the email pipeline reads. */
|
|
491
|
+
declare function emailGroupFrom(row: Record<string, unknown>): EmailGroup;
|
|
492
|
+
|
|
436
493
|
/**
|
|
437
494
|
* Apply defaults + validate the pipeline config. With no config, the full Silver
|
|
438
495
|
* & Salt pipeline. With `stages` given but the subsets omitted, the subsets
|
|
@@ -619,14 +676,69 @@ interface ProjectionDeps {
|
|
|
619
676
|
}
|
|
620
677
|
/**
|
|
621
678
|
* Upsert a hub-shared prospect into this chapter's `crm_record` (push
|
|
622
|
-
* projection)
|
|
623
|
-
*
|
|
624
|
-
* the chapter-side record id. Callers wrap this in `.catch` so a projection
|
|
625
|
-
* failure never fails the hub's share request.
|
|
679
|
+
* projection), idempotent by the hub record id and unified by email. Callers wrap
|
|
680
|
+
* this in `.catch` so a projection failure never fails the hub's share request.
|
|
626
681
|
*/
|
|
627
682
|
declare function projectSharedRecord(deps: ProjectionDeps, person: SharedPerson): Promise<{
|
|
628
683
|
recordId: string;
|
|
629
684
|
}>;
|
|
685
|
+
/** An arriving applicant, as far as the CRM projection cares. */
|
|
686
|
+
interface Applicant {
|
|
687
|
+
applicationId: string;
|
|
688
|
+
email: string;
|
|
689
|
+
firstName?: string;
|
|
690
|
+
lastName?: string;
|
|
691
|
+
phone?: string;
|
|
692
|
+
linkedin?: string;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Project an arriving applicant into this chapter's `crm_record`, so a new
|
|
696
|
+
* application shows up in the CRM immediately, unified by email with any prior
|
|
697
|
+
* record. Idempotent per application (`apply:${applicationId}`). Best-effort at
|
|
698
|
+
* the call site — a projection failure never fails the application.
|
|
699
|
+
*/
|
|
700
|
+
declare function projectApplicant(deps: ProjectionDeps, applicant: Applicant): Promise<{
|
|
701
|
+
recordId: string;
|
|
702
|
+
}>;
|
|
703
|
+
|
|
704
|
+
/** Inputs for a Clerk invitation. */
|
|
705
|
+
interface ClerkInviteInput {
|
|
706
|
+
email: string;
|
|
707
|
+
/** Where the accept-invitation link lands (usually the member area). */
|
|
708
|
+
redirectUrl?: string;
|
|
709
|
+
}
|
|
710
|
+
/** Build the Clerk Backend API invitation request (path + JSON body). Pure, so
|
|
711
|
+
* the wire shape is testable without a network call. */
|
|
712
|
+
declare function clerkInviteRequest(input: ClerkInviteInput): {
|
|
713
|
+
path: string;
|
|
714
|
+
body: Record<string, unknown>;
|
|
715
|
+
};
|
|
716
|
+
/** POST the invitation to the Clerk Backend API. `ignore_existing` isn't set, so
|
|
717
|
+
* a repeat invite for an already-invited/known email returns non-ok — the caller
|
|
718
|
+
* swallows that (idempotent-enough for a best-effort apply-time invite). */
|
|
719
|
+
declare function createClerkInvitation(secretKey: string, input: ClerkInviteInput, fetchImpl?: typeof fetch): Promise<{
|
|
720
|
+
ok: boolean;
|
|
721
|
+
status: number;
|
|
722
|
+
}>;
|
|
723
|
+
/** Inputs for a server-side Clerk user create. */
|
|
724
|
+
interface ClerkUserInput {
|
|
725
|
+
email: string;
|
|
726
|
+
firstName?: string;
|
|
727
|
+
lastName?: string;
|
|
728
|
+
}
|
|
729
|
+
/** Build the Clerk Backend API user-create request. The account is created
|
|
730
|
+
* passwordless (the member signs in via the site's Clerk flow), so join step 3
|
|
731
|
+
* can say the account is ready — the "create" alternative to an invitation. */
|
|
732
|
+
declare function clerkUserRequest(input: ClerkUserInput): {
|
|
733
|
+
path: string;
|
|
734
|
+
body: Record<string, unknown>;
|
|
735
|
+
};
|
|
736
|
+
/** Create the applicant's Clerk account server-side. A repeat for a known email
|
|
737
|
+
* returns non-ok; the caller swallows it (best-effort at apply time). */
|
|
738
|
+
declare function createClerkUser(secretKey: string, input: ClerkUserInput, fetchImpl?: typeof fetch): Promise<{
|
|
739
|
+
ok: boolean;
|
|
740
|
+
status: number;
|
|
741
|
+
}>;
|
|
630
742
|
|
|
631
743
|
/** An application row, as far as the session cares about it. */
|
|
632
744
|
interface ApplicationRecord {
|
|
@@ -706,6 +818,45 @@ declare function memberSession(user: SessionUser, opts: {
|
|
|
706
818
|
*/
|
|
707
819
|
declare function brandTokens(brand: ChapterBrand | undefined): string;
|
|
708
820
|
|
|
821
|
+
/** A `meetings` row, as far as reconciliation cares. */
|
|
822
|
+
interface MeetingForReconcile {
|
|
823
|
+
id: string;
|
|
824
|
+
applicationId: string;
|
|
825
|
+
googleEventId?: string | null;
|
|
826
|
+
status: string;
|
|
827
|
+
startAt?: number | null;
|
|
828
|
+
endAt?: number | null;
|
|
829
|
+
}
|
|
830
|
+
/** One live calendar event (a subset of @odla-ai/calendar's Booking). */
|
|
831
|
+
interface LiveEvent {
|
|
832
|
+
eventId: string;
|
|
833
|
+
status?: string;
|
|
834
|
+
startAt?: number;
|
|
835
|
+
endAt?: number;
|
|
836
|
+
}
|
|
837
|
+
/** An adopt decision: mirror a Google move/cancel onto our rows. */
|
|
838
|
+
interface ReconcileDecision {
|
|
839
|
+
meetingId: string;
|
|
840
|
+
applicationId: string;
|
|
841
|
+
kind: "cancelled" | "moved";
|
|
842
|
+
/** Attrs to write onto the `meetings` row. */
|
|
843
|
+
meetingPatch: Record<string, unknown>;
|
|
844
|
+
/** Attrs to write onto the `applications` row (the projection). */
|
|
845
|
+
applicationPatch: Record<string, unknown>;
|
|
846
|
+
}
|
|
847
|
+
/** Meetings still worth reconciling: a scheduled booking with a Google event that
|
|
848
|
+
* starts in the future (or within the last hour, to catch a just-passed edit). */
|
|
849
|
+
declare function isReconcilable(meeting: MeetingForReconcile, now: number): boolean;
|
|
850
|
+
/**
|
|
851
|
+
* Diff canonical `meetings` against the live calendar events and return the adopt
|
|
852
|
+
* decisions — only for meetings that actually changed (a still-matching meeting
|
|
853
|
+
* is omitted). A meeting whose event vanished or is `cancelled` in Google is
|
|
854
|
+
* adopted as cancelled (application `meetingAt` zeroed — 0 means "was booked,
|
|
855
|
+
* then cancelled"); a meeting whose event moved adopts the new window (duration
|
|
856
|
+
* preserved when the event omits `endAt`).
|
|
857
|
+
*/
|
|
858
|
+
declare function reconcileMeetings(meetings: readonly MeetingForReconcile[], events: readonly LiveEvent[], now: number): ReconcileDecision[];
|
|
859
|
+
|
|
709
860
|
/** A fully-resolved scheduling config (every field present). */
|
|
710
861
|
interface ResolvedScheduling {
|
|
711
862
|
slotMinutes: number;
|
|
@@ -729,9 +880,6 @@ declare const SCHEDULING_DEFAULTS: ResolvedScheduling;
|
|
|
729
880
|
*/
|
|
730
881
|
declare function resolveScheduling(config?: ChapterScheduling): ResolvedScheduling;
|
|
731
882
|
/** Statuses a member may book/reschedule from (early pipeline only). */
|
|
732
|
-
declare const BOOKABLE_STATUSES: readonly string[];
|
|
733
|
-
/** Whether an application at `status` may book a call. */
|
|
734
|
-
declare function canBookFrom(status: string): boolean;
|
|
735
883
|
/** The availability window: `[now, now + windowDays]` in epoch ms. */
|
|
736
884
|
declare function slotWindow(now: number, windowDays: number): {
|
|
737
885
|
from: number;
|
|
@@ -814,4 +962,4 @@ type ApplicationBookingPatch = {
|
|
|
814
962
|
* already there (never backward). */
|
|
815
963
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
816
964
|
|
|
817
|
-
export { type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType,
|
|
965
|
+
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 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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -210,7 +210,14 @@ interface ChapterConfig {
|
|
|
210
210
|
auth?: ChapterAuth;
|
|
211
211
|
/** odla services (db implied). Default `["db","calendar","o11y"]`. */
|
|
212
212
|
services?: readonly string[];
|
|
213
|
-
|
|
213
|
+
/** Apply-time account provisioning: `"invite"` (default) mints a Clerk
|
|
214
|
+
* invitation, `"create"` makes the account server-side (so join can say the
|
|
215
|
+
* account is ready), `"none"` skips it. Any of these needs a `clerk_secret_key`
|
|
216
|
+
* vault secret to act. */
|
|
217
|
+
account?: AccountModel;
|
|
218
|
+
}
|
|
219
|
+
/** Apply-time Clerk account provisioning model. */
|
|
220
|
+
type AccountModel = "invite" | "create" | "none";
|
|
214
221
|
/** The resolved engine `defineChapter()` returns. */
|
|
215
222
|
interface Chapter {
|
|
216
223
|
config: ChapterConfig;
|
|
@@ -230,6 +237,8 @@ interface Chapter {
|
|
|
230
237
|
schema: DbSchema;
|
|
231
238
|
rules: DbRules;
|
|
232
239
|
services: readonly string[];
|
|
240
|
+
/** Resolved apply-time account provisioning model (default `"invite"`). */
|
|
241
|
+
account: AccountModel;
|
|
233
242
|
/** The seed `groups` row derived from config (chapter mode), else `null`. */
|
|
234
243
|
groupSeed(): Record<string, unknown> | null;
|
|
235
244
|
}
|
|
@@ -433,6 +442,54 @@ declare function planDelivery(input: {
|
|
|
433
442
|
force?: boolean;
|
|
434
443
|
}): DeliveryDecision;
|
|
435
444
|
|
|
445
|
+
/** The mail transport (the worker's Cloudflare SEND_EMAIL binding). */
|
|
446
|
+
interface MailSender {
|
|
447
|
+
send(payload: {
|
|
448
|
+
from: string;
|
|
449
|
+
to: string[];
|
|
450
|
+
subject: string;
|
|
451
|
+
text?: string;
|
|
452
|
+
replyTo?: string;
|
|
453
|
+
}): Promise<{
|
|
454
|
+
messageId: string;
|
|
455
|
+
}>;
|
|
456
|
+
}
|
|
457
|
+
/** Deps for {@link sendTemplated} — the db, the env name (drives the fail-safe),
|
|
458
|
+
* the transport + from (absent ⇒ log-only), and injected clock/id. */
|
|
459
|
+
interface NotifyDeps {
|
|
460
|
+
db: ChapterDb;
|
|
461
|
+
envName: string;
|
|
462
|
+
sender?: MailSender;
|
|
463
|
+
from?: string;
|
|
464
|
+
now: () => number;
|
|
465
|
+
newId: () => string;
|
|
466
|
+
}
|
|
467
|
+
/** One templated send. `dedupeKey` is the exactly-once key. */
|
|
468
|
+
interface NotifyInput {
|
|
469
|
+
group: EmailGroup;
|
|
470
|
+
template: string;
|
|
471
|
+
to: string;
|
|
472
|
+
vars: Record<string, string>;
|
|
473
|
+
dedupeKey: string;
|
|
474
|
+
applicationId?: string;
|
|
475
|
+
/** The admin test route may send a disabled template. */
|
|
476
|
+
force?: boolean;
|
|
477
|
+
}
|
|
478
|
+
/** The outcome. `sent:true` includes the already-sent short-circuit. */
|
|
479
|
+
interface NotifyResult {
|
|
480
|
+
sent: boolean;
|
|
481
|
+
reason?: string;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Send a templated lifecycle email exactly once. Short-circuits on a prior
|
|
485
|
+
* successful send; otherwise plans delivery (dev fail-safe applies), sends via the
|
|
486
|
+
* transport when one is wired and the plan calls for it, and records an emailLog
|
|
487
|
+
* row either way (success keyed for exactly-once, failure unkeyed for retry).
|
|
488
|
+
*/
|
|
489
|
+
declare function sendTemplated(deps: NotifyDeps, input: NotifyInput): Promise<NotifyResult>;
|
|
490
|
+
/** Project a `groups` row into the {@link EmailGroup} the email pipeline reads. */
|
|
491
|
+
declare function emailGroupFrom(row: Record<string, unknown>): EmailGroup;
|
|
492
|
+
|
|
436
493
|
/**
|
|
437
494
|
* Apply defaults + validate the pipeline config. With no config, the full Silver
|
|
438
495
|
* & Salt pipeline. With `stages` given but the subsets omitted, the subsets
|
|
@@ -619,14 +676,69 @@ interface ProjectionDeps {
|
|
|
619
676
|
}
|
|
620
677
|
/**
|
|
621
678
|
* Upsert a hub-shared prospect into this chapter's `crm_record` (push
|
|
622
|
-
* projection)
|
|
623
|
-
*
|
|
624
|
-
* the chapter-side record id. Callers wrap this in `.catch` so a projection
|
|
625
|
-
* failure never fails the hub's share request.
|
|
679
|
+
* projection), idempotent by the hub record id and unified by email. Callers wrap
|
|
680
|
+
* this in `.catch` so a projection failure never fails the hub's share request.
|
|
626
681
|
*/
|
|
627
682
|
declare function projectSharedRecord(deps: ProjectionDeps, person: SharedPerson): Promise<{
|
|
628
683
|
recordId: string;
|
|
629
684
|
}>;
|
|
685
|
+
/** An arriving applicant, as far as the CRM projection cares. */
|
|
686
|
+
interface Applicant {
|
|
687
|
+
applicationId: string;
|
|
688
|
+
email: string;
|
|
689
|
+
firstName?: string;
|
|
690
|
+
lastName?: string;
|
|
691
|
+
phone?: string;
|
|
692
|
+
linkedin?: string;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Project an arriving applicant into this chapter's `crm_record`, so a new
|
|
696
|
+
* application shows up in the CRM immediately, unified by email with any prior
|
|
697
|
+
* record. Idempotent per application (`apply:${applicationId}`). Best-effort at
|
|
698
|
+
* the call site — a projection failure never fails the application.
|
|
699
|
+
*/
|
|
700
|
+
declare function projectApplicant(deps: ProjectionDeps, applicant: Applicant): Promise<{
|
|
701
|
+
recordId: string;
|
|
702
|
+
}>;
|
|
703
|
+
|
|
704
|
+
/** Inputs for a Clerk invitation. */
|
|
705
|
+
interface ClerkInviteInput {
|
|
706
|
+
email: string;
|
|
707
|
+
/** Where the accept-invitation link lands (usually the member area). */
|
|
708
|
+
redirectUrl?: string;
|
|
709
|
+
}
|
|
710
|
+
/** Build the Clerk Backend API invitation request (path + JSON body). Pure, so
|
|
711
|
+
* the wire shape is testable without a network call. */
|
|
712
|
+
declare function clerkInviteRequest(input: ClerkInviteInput): {
|
|
713
|
+
path: string;
|
|
714
|
+
body: Record<string, unknown>;
|
|
715
|
+
};
|
|
716
|
+
/** POST the invitation to the Clerk Backend API. `ignore_existing` isn't set, so
|
|
717
|
+
* a repeat invite for an already-invited/known email returns non-ok — the caller
|
|
718
|
+
* swallows that (idempotent-enough for a best-effort apply-time invite). */
|
|
719
|
+
declare function createClerkInvitation(secretKey: string, input: ClerkInviteInput, fetchImpl?: typeof fetch): Promise<{
|
|
720
|
+
ok: boolean;
|
|
721
|
+
status: number;
|
|
722
|
+
}>;
|
|
723
|
+
/** Inputs for a server-side Clerk user create. */
|
|
724
|
+
interface ClerkUserInput {
|
|
725
|
+
email: string;
|
|
726
|
+
firstName?: string;
|
|
727
|
+
lastName?: string;
|
|
728
|
+
}
|
|
729
|
+
/** Build the Clerk Backend API user-create request. The account is created
|
|
730
|
+
* passwordless (the member signs in via the site's Clerk flow), so join step 3
|
|
731
|
+
* can say the account is ready — the "create" alternative to an invitation. */
|
|
732
|
+
declare function clerkUserRequest(input: ClerkUserInput): {
|
|
733
|
+
path: string;
|
|
734
|
+
body: Record<string, unknown>;
|
|
735
|
+
};
|
|
736
|
+
/** Create the applicant's Clerk account server-side. A repeat for a known email
|
|
737
|
+
* returns non-ok; the caller swallows it (best-effort at apply time). */
|
|
738
|
+
declare function createClerkUser(secretKey: string, input: ClerkUserInput, fetchImpl?: typeof fetch): Promise<{
|
|
739
|
+
ok: boolean;
|
|
740
|
+
status: number;
|
|
741
|
+
}>;
|
|
630
742
|
|
|
631
743
|
/** An application row, as far as the session cares about it. */
|
|
632
744
|
interface ApplicationRecord {
|
|
@@ -706,6 +818,45 @@ declare function memberSession(user: SessionUser, opts: {
|
|
|
706
818
|
*/
|
|
707
819
|
declare function brandTokens(brand: ChapterBrand | undefined): string;
|
|
708
820
|
|
|
821
|
+
/** A `meetings` row, as far as reconciliation cares. */
|
|
822
|
+
interface MeetingForReconcile {
|
|
823
|
+
id: string;
|
|
824
|
+
applicationId: string;
|
|
825
|
+
googleEventId?: string | null;
|
|
826
|
+
status: string;
|
|
827
|
+
startAt?: number | null;
|
|
828
|
+
endAt?: number | null;
|
|
829
|
+
}
|
|
830
|
+
/** One live calendar event (a subset of @odla-ai/calendar's Booking). */
|
|
831
|
+
interface LiveEvent {
|
|
832
|
+
eventId: string;
|
|
833
|
+
status?: string;
|
|
834
|
+
startAt?: number;
|
|
835
|
+
endAt?: number;
|
|
836
|
+
}
|
|
837
|
+
/** An adopt decision: mirror a Google move/cancel onto our rows. */
|
|
838
|
+
interface ReconcileDecision {
|
|
839
|
+
meetingId: string;
|
|
840
|
+
applicationId: string;
|
|
841
|
+
kind: "cancelled" | "moved";
|
|
842
|
+
/** Attrs to write onto the `meetings` row. */
|
|
843
|
+
meetingPatch: Record<string, unknown>;
|
|
844
|
+
/** Attrs to write onto the `applications` row (the projection). */
|
|
845
|
+
applicationPatch: Record<string, unknown>;
|
|
846
|
+
}
|
|
847
|
+
/** Meetings still worth reconciling: a scheduled booking with a Google event that
|
|
848
|
+
* starts in the future (or within the last hour, to catch a just-passed edit). */
|
|
849
|
+
declare function isReconcilable(meeting: MeetingForReconcile, now: number): boolean;
|
|
850
|
+
/**
|
|
851
|
+
* Diff canonical `meetings` against the live calendar events and return the adopt
|
|
852
|
+
* decisions — only for meetings that actually changed (a still-matching meeting
|
|
853
|
+
* is omitted). A meeting whose event vanished or is `cancelled` in Google is
|
|
854
|
+
* adopted as cancelled (application `meetingAt` zeroed — 0 means "was booked,
|
|
855
|
+
* then cancelled"); a meeting whose event moved adopts the new window (duration
|
|
856
|
+
* preserved when the event omits `endAt`).
|
|
857
|
+
*/
|
|
858
|
+
declare function reconcileMeetings(meetings: readonly MeetingForReconcile[], events: readonly LiveEvent[], now: number): ReconcileDecision[];
|
|
859
|
+
|
|
709
860
|
/** A fully-resolved scheduling config (every field present). */
|
|
710
861
|
interface ResolvedScheduling {
|
|
711
862
|
slotMinutes: number;
|
|
@@ -729,9 +880,6 @@ declare const SCHEDULING_DEFAULTS: ResolvedScheduling;
|
|
|
729
880
|
*/
|
|
730
881
|
declare function resolveScheduling(config?: ChapterScheduling): ResolvedScheduling;
|
|
731
882
|
/** Statuses a member may book/reschedule from (early pipeline only). */
|
|
732
|
-
declare const BOOKABLE_STATUSES: readonly string[];
|
|
733
|
-
/** Whether an application at `status` may book a call. */
|
|
734
|
-
declare function canBookFrom(status: string): boolean;
|
|
735
883
|
/** The availability window: `[now, now + windowDays]` in epoch ms. */
|
|
736
884
|
declare function slotWindow(now: number, windowDays: number): {
|
|
737
885
|
from: number;
|
|
@@ -814,4 +962,4 @@ type ApplicationBookingPatch = {
|
|
|
814
962
|
* already there (never backward). */
|
|
815
963
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
816
964
|
|
|
817
|
-
export { type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType,
|
|
965
|
+
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 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 };
|