@odla-ai/chapter 0.31.2 → 0.31.4
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 +8 -0
- package/dist/{copy-context-HAhNTZdl.d.ts → copy-context-BXBEdp4p.d.ts} +3 -3
- package/dist/index.cjs +203 -199
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -24
- package/dist/index.d.ts +13 -24
- package/dist/index.js +201 -197
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +2 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/member/index.d.ts +2 -2
- package/dist/worker/index.cjs +10 -56
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +3 -3
- package/dist/worker/index.d.ts +3 -3
- package/dist/worker/index.js +10 -56
- package/dist/worker/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { CrmConfig, Crm, FieldConditions, TypeDef, FieldDef, TypeSummary, CrmRecord } from '@odla-ai/crm';
|
|
2
|
+
import { StripeRequestResult } from '@odla-ai/stripe';
|
|
3
|
+
export { stripeForm, verifyStripeSignature } from '@odla-ai/stripe';
|
|
2
4
|
|
|
3
5
|
/** One membership tier a chapter offers. `priceCents: 0` means free. */
|
|
4
6
|
interface ChapterTier {
|
|
@@ -479,9 +481,9 @@ interface ResolvedPipeline {
|
|
|
479
481
|
}
|
|
480
482
|
/** The application (join form) validation surface — which string fields are
|
|
481
483
|
* required vs accepted, their max lengths, and the request body cap. Drives
|
|
482
|
-
* submit validation
|
|
483
|
-
*
|
|
484
|
-
*
|
|
484
|
+
* submit validation, the generated `applications` schema, and the CRM slot
|
|
485
|
+
* projection; defaults to the reference form. Built-in fields retain their
|
|
486
|
+
* declared schema types/indexes, and site-defined fields become string attrs. */
|
|
485
487
|
interface ChapterApplication {
|
|
486
488
|
required?: readonly string[];
|
|
487
489
|
optional?: readonly string[];
|
|
@@ -759,6 +761,11 @@ interface ChapterRunbookHints {
|
|
|
759
761
|
declare function createChapterIntegration(chapter: Chapter, options?: ChapterIntegrationOptions): ChapterIntegrationDescriptor;
|
|
760
762
|
|
|
761
763
|
/** The chapter's own schema + deny-all rules for a mode + auth policy.
|
|
764
|
+
*
|
|
765
|
+
* In chapter mode, the optional {@link ResolvedApplication} supplies the exact
|
|
766
|
+
* required/optional application string attrs. Omit it for the reference form.
|
|
767
|
+
* Built-in attrs retain their types/indexes; site-defined fields become string
|
|
768
|
+
* attrs, while package-owned operational attrs cannot be repurposed as inputs.
|
|
762
769
|
*
|
|
763
770
|
* Operational tables (`applications`/`groups`/`meetings`/`emailLog`) are added in
|
|
764
771
|
* `chapter` mode only. The auth tables follow {@link ResolvedAuth}: `source:
|
|
@@ -766,7 +773,7 @@ declare function createChapterIntegration(chapter: Chapter, options?: ChapterInt
|
|
|
766
773
|
* read-only super-admin tier (default on for the `"claim"` ladder). A `"claim"`
|
|
767
774
|
* chapter therefore emits the reference namespace set — `applications`,
|
|
768
775
|
* `groups`, `meetings`, `emailLog`, `superAdmins` — with no `admins` table. */
|
|
769
|
-
declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth, includeNetworkNotes?: boolean): {
|
|
776
|
+
declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth, includeNetworkNotes?: boolean, application?: ResolvedApplication): {
|
|
770
777
|
schema: DbSchema;
|
|
771
778
|
rules: DbRules;
|
|
772
779
|
};
|
|
@@ -979,17 +986,6 @@ declare function canBook(status: string, p: ResolvedPipeline): boolean;
|
|
|
979
986
|
/** May an application be approved (→ member) from this status? */
|
|
980
987
|
declare function canApprove(status: string, p: ResolvedPipeline): boolean;
|
|
981
988
|
|
|
982
|
-
/**
|
|
983
|
-
* Verify a Stripe webhook signature (C3): HMAC-SHA256 over `` `${t}.${payload}` ``
|
|
984
|
-
* with the endpoint signing secret, a replay window (default 5 minutes), and a
|
|
985
|
-
* constant-time compare. Package-enforced — never left to a site. Returns `false`
|
|
986
|
-
* (never throws) on a malformed header, a non-numeric or stale timestamp, or a
|
|
987
|
-
* signature mismatch. `now`/`toleranceSec` are injectable for tests.
|
|
988
|
-
*/
|
|
989
|
-
declare function verifyStripeSignature(payload: string, header: string, secret: string, opts?: {
|
|
990
|
-
now?: number;
|
|
991
|
-
toleranceSec?: number;
|
|
992
|
-
}): Promise<boolean>;
|
|
993
989
|
/** A group row's payment configuration, as far as readiness cares. */
|
|
994
990
|
interface PaymentsGroup {
|
|
995
991
|
stripePublishableKey?: string | null;
|
|
@@ -999,9 +995,6 @@ interface PaymentsGroup {
|
|
|
999
995
|
* group row) AND a secret key (the vault). Anything missing drops the join
|
|
1000
996
|
* flow's payment step (paymentsReady:false) rather than half-charging. */
|
|
1001
997
|
declare function paymentsReady(group: PaymentsGroup, hasSecretKey: boolean): boolean;
|
|
1002
|
-
/** Form-encode params for Stripe's x-www-form-urlencoded API, expanding one level
|
|
1003
|
-
* of nested objects into bracket syntax (`metadata[applicationId]=...`). */
|
|
1004
|
-
declare function stripeForm(params: Record<string, unknown>): string;
|
|
1005
998
|
/** The Stripe idempotency key for creating an application's subscription — one
|
|
1006
999
|
* per application, so a client retry can't orphan a second subscription. */
|
|
1007
1000
|
declare function subscriptionIdempotencyKey(applicationId: string): string;
|
|
@@ -1400,11 +1393,7 @@ declare function dashboardMetricData(points: Array<{
|
|
|
1400
1393
|
declare function subAnnualCents(sub: Record<string, unknown>): number;
|
|
1401
1394
|
|
|
1402
1395
|
/** The result of a Stripe Backend API call: ok + status + parsed JSON body. */
|
|
1403
|
-
type StripeResult =
|
|
1404
|
-
ok: boolean;
|
|
1405
|
-
status: number;
|
|
1406
|
-
body: Record<string, unknown>;
|
|
1407
|
-
};
|
|
1396
|
+
type StripeResult = StripeRequestResult<Record<string, unknown>>;
|
|
1408
1397
|
/** Call the Stripe Backend API (form-encoded, Bearer sk_). Exposed so the admin
|
|
1409
1398
|
* billing/dashboard reads and the refund route share one Stripe client instead
|
|
1410
1399
|
* of each re-deriving the auth + encoding. */
|
|
@@ -1863,4 +1852,4 @@ declare function loadTiers(db: ChapterDb, group: TierGroupRow): Promise<ChapterT
|
|
|
1863
1852
|
/** The tiers a join page may show: active, and joinable as configured. */
|
|
1864
1853
|
declare function loadOfferableTiers(db: ChapterDb, group: TierGroupRow, hasSecretKey: boolean): Promise<ChapterTier[]>;
|
|
1865
1854
|
|
|
1866
|
-
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 ChapterCopy, type ChapterCopyInput, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkReader, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, type ChapterTier, type ChapterTierConfig, type ClerkInviteInput, type ClerkMetadataInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, type CompiledChapterBrandTokens, DEFAULT_CHAPTER_COPY, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type JoinConfigTier, type LeaderCrmOptions, type LeaderFormationConfig, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NetworkRecord, type NetworkRecordNote, type NetworkRecordPage, type NetworkRollup, type NetworkRollupTarget, type NetworkSharedNote, type NetworkSnapshot, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedLeaderFormation, type ResolvedNetwork, type ResolvedNetworkReader, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type SharedRecordV1, type SharedRecordV2, type StripeEvent, type StripeResult, type SubmitResult, type TierGroup, type TierGroupRow, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, buildTierSeeds, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, findTier, firstPaymentPatch, formatChapterCopy, formationFields, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, joinConfigTiers, leaderCrmConfig, loadOfferableTiers, loadTiers, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, offerableTiers, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolveLeaderFormation, resolvePipeline, resolveScheduling, resolveTiers, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall,
|
|
1855
|
+
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 ChapterCopy, type ChapterCopyInput, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkReader, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, type ChapterTier, type ChapterTierConfig, type ClerkInviteInput, type ClerkMetadataInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, type CompiledChapterBrandTokens, DEFAULT_CHAPTER_COPY, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type JoinConfigTier, type LeaderCrmOptions, type LeaderFormationConfig, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NetworkRecord, type NetworkRecordNote, type NetworkRecordPage, type NetworkRollup, type NetworkRollupTarget, type NetworkSharedNote, type NetworkSnapshot, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedLeaderFormation, type ResolvedNetwork, type ResolvedNetworkReader, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type SharedRecordV1, type SharedRecordV2, type StripeEvent, type StripeResult, type SubmitResult, type TierGroup, type TierGroupRow, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, buildTierSeeds, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, findTier, firstPaymentPatch, formatChapterCopy, formationFields, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, joinConfigTiers, leaderCrmConfig, loadOfferableTiers, loadTiers, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, offerableTiers, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolveLeaderFormation, resolvePipeline, resolveScheduling, resolveTiers, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, tierIsFree, tierPayable, updateClerkUserMetadata, updateClerkUserMetadataByEmail, validateScheduling, webhookMutationId };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { CrmConfig, Crm, FieldConditions, TypeDef, FieldDef, TypeSummary, CrmRecord } from '@odla-ai/crm';
|
|
2
|
+
import { StripeRequestResult } from '@odla-ai/stripe';
|
|
3
|
+
export { stripeForm, verifyStripeSignature } from '@odla-ai/stripe';
|
|
2
4
|
|
|
3
5
|
/** One membership tier a chapter offers. `priceCents: 0` means free. */
|
|
4
6
|
interface ChapterTier {
|
|
@@ -479,9 +481,9 @@ interface ResolvedPipeline {
|
|
|
479
481
|
}
|
|
480
482
|
/** The application (join form) validation surface — which string fields are
|
|
481
483
|
* required vs accepted, their max lengths, and the request body cap. Drives
|
|
482
|
-
* submit validation
|
|
483
|
-
*
|
|
484
|
-
*
|
|
484
|
+
* submit validation, the generated `applications` schema, and the CRM slot
|
|
485
|
+
* projection; defaults to the reference form. Built-in fields retain their
|
|
486
|
+
* declared schema types/indexes, and site-defined fields become string attrs. */
|
|
485
487
|
interface ChapterApplication {
|
|
486
488
|
required?: readonly string[];
|
|
487
489
|
optional?: readonly string[];
|
|
@@ -759,6 +761,11 @@ interface ChapterRunbookHints {
|
|
|
759
761
|
declare function createChapterIntegration(chapter: Chapter, options?: ChapterIntegrationOptions): ChapterIntegrationDescriptor;
|
|
760
762
|
|
|
761
763
|
/** The chapter's own schema + deny-all rules for a mode + auth policy.
|
|
764
|
+
*
|
|
765
|
+
* In chapter mode, the optional {@link ResolvedApplication} supplies the exact
|
|
766
|
+
* required/optional application string attrs. Omit it for the reference form.
|
|
767
|
+
* Built-in attrs retain their types/indexes; site-defined fields become string
|
|
768
|
+
* attrs, while package-owned operational attrs cannot be repurposed as inputs.
|
|
762
769
|
*
|
|
763
770
|
* Operational tables (`applications`/`groups`/`meetings`/`emailLog`) are added in
|
|
764
771
|
* `chapter` mode only. The auth tables follow {@link ResolvedAuth}: `source:
|
|
@@ -766,7 +773,7 @@ declare function createChapterIntegration(chapter: Chapter, options?: ChapterInt
|
|
|
766
773
|
* read-only super-admin tier (default on for the `"claim"` ladder). A `"claim"`
|
|
767
774
|
* chapter therefore emits the reference namespace set — `applications`,
|
|
768
775
|
* `groups`, `meetings`, `emailLog`, `superAdmins` — with no `admins` table. */
|
|
769
|
-
declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth, includeNetworkNotes?: boolean): {
|
|
776
|
+
declare function chapterDb(mode: ChapterMode, auth: ResolvedAuth, includeNetworkNotes?: boolean, application?: ResolvedApplication): {
|
|
770
777
|
schema: DbSchema;
|
|
771
778
|
rules: DbRules;
|
|
772
779
|
};
|
|
@@ -979,17 +986,6 @@ declare function canBook(status: string, p: ResolvedPipeline): boolean;
|
|
|
979
986
|
/** May an application be approved (→ member) from this status? */
|
|
980
987
|
declare function canApprove(status: string, p: ResolvedPipeline): boolean;
|
|
981
988
|
|
|
982
|
-
/**
|
|
983
|
-
* Verify a Stripe webhook signature (C3): HMAC-SHA256 over `` `${t}.${payload}` ``
|
|
984
|
-
* with the endpoint signing secret, a replay window (default 5 minutes), and a
|
|
985
|
-
* constant-time compare. Package-enforced — never left to a site. Returns `false`
|
|
986
|
-
* (never throws) on a malformed header, a non-numeric or stale timestamp, or a
|
|
987
|
-
* signature mismatch. `now`/`toleranceSec` are injectable for tests.
|
|
988
|
-
*/
|
|
989
|
-
declare function verifyStripeSignature(payload: string, header: string, secret: string, opts?: {
|
|
990
|
-
now?: number;
|
|
991
|
-
toleranceSec?: number;
|
|
992
|
-
}): Promise<boolean>;
|
|
993
989
|
/** A group row's payment configuration, as far as readiness cares. */
|
|
994
990
|
interface PaymentsGroup {
|
|
995
991
|
stripePublishableKey?: string | null;
|
|
@@ -999,9 +995,6 @@ interface PaymentsGroup {
|
|
|
999
995
|
* group row) AND a secret key (the vault). Anything missing drops the join
|
|
1000
996
|
* flow's payment step (paymentsReady:false) rather than half-charging. */
|
|
1001
997
|
declare function paymentsReady(group: PaymentsGroup, hasSecretKey: boolean): boolean;
|
|
1002
|
-
/** Form-encode params for Stripe's x-www-form-urlencoded API, expanding one level
|
|
1003
|
-
* of nested objects into bracket syntax (`metadata[applicationId]=...`). */
|
|
1004
|
-
declare function stripeForm(params: Record<string, unknown>): string;
|
|
1005
998
|
/** The Stripe idempotency key for creating an application's subscription — one
|
|
1006
999
|
* per application, so a client retry can't orphan a second subscription. */
|
|
1007
1000
|
declare function subscriptionIdempotencyKey(applicationId: string): string;
|
|
@@ -1400,11 +1393,7 @@ declare function dashboardMetricData(points: Array<{
|
|
|
1400
1393
|
declare function subAnnualCents(sub: Record<string, unknown>): number;
|
|
1401
1394
|
|
|
1402
1395
|
/** The result of a Stripe Backend API call: ok + status + parsed JSON body. */
|
|
1403
|
-
type StripeResult =
|
|
1404
|
-
ok: boolean;
|
|
1405
|
-
status: number;
|
|
1406
|
-
body: Record<string, unknown>;
|
|
1407
|
-
};
|
|
1396
|
+
type StripeResult = StripeRequestResult<Record<string, unknown>>;
|
|
1408
1397
|
/** Call the Stripe Backend API (form-encoded, Bearer sk_). Exposed so the admin
|
|
1409
1398
|
* billing/dashboard reads and the refund route share one Stripe client instead
|
|
1410
1399
|
* of each re-deriving the auth + encoding. */
|
|
@@ -1863,4 +1852,4 @@ declare function loadTiers(db: ChapterDb, group: TierGroupRow): Promise<ChapterT
|
|
|
1863
1852
|
/** The tiers a join page may show: active, and joinable as configured. */
|
|
1864
1853
|
declare function loadOfferableTiers(db: ChapterDb, group: TierGroupRow, hasSecretKey: boolean): Promise<ChapterTier[]>;
|
|
1865
1854
|
|
|
1866
|
-
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 ChapterCopy, type ChapterCopyInput, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkReader, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, type ChapterTier, type ChapterTierConfig, type ClerkInviteInput, type ClerkMetadataInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, type CompiledChapterBrandTokens, DEFAULT_CHAPTER_COPY, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type JoinConfigTier, type LeaderCrmOptions, type LeaderFormationConfig, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NetworkRecord, type NetworkRecordNote, type NetworkRecordPage, type NetworkRollup, type NetworkRollupTarget, type NetworkSharedNote, type NetworkSnapshot, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedLeaderFormation, type ResolvedNetwork, type ResolvedNetworkReader, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type SharedRecordV1, type SharedRecordV2, type StripeEvent, type StripeResult, type SubmitResult, type TierGroup, type TierGroupRow, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, buildTierSeeds, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, findTier, firstPaymentPatch, formatChapterCopy, formationFields, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, joinConfigTiers, leaderCrmConfig, loadOfferableTiers, loadTiers, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, offerableTiers, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolveLeaderFormation, resolvePipeline, resolveScheduling, resolveTiers, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall,
|
|
1855
|
+
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 ChapterCopy, type ChapterCopyInput, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkReader, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, type ChapterTier, type ChapterTierConfig, type ClerkInviteInput, type ClerkMetadataInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, type CompiledChapterBrandTokens, DEFAULT_CHAPTER_COPY, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type JoinConfigTier, type LeaderCrmOptions, type LeaderFormationConfig, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NetworkRecord, type NetworkRecordNote, type NetworkRecordPage, type NetworkRollup, type NetworkRollupTarget, type NetworkSharedNote, type NetworkSnapshot, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedLeaderFormation, type ResolvedNetwork, type ResolvedNetworkReader, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type SharedRecordV1, type SharedRecordV2, type StripeEvent, type StripeResult, type SubmitResult, type TierGroup, type TierGroupRow, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, buildTierSeeds, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, findTier, firstPaymentPatch, formatChapterCopy, formationFields, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, joinConfigTiers, leaderCrmConfig, loadOfferableTiers, loadTiers, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, offerableTiers, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolveLeaderFormation, resolvePipeline, resolveScheduling, resolveTiers, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, tierIsFree, tierPayable, updateClerkUserMetadata, updateClerkUserMetadataByEmail, validateScheduling, webhookMutationId };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,154 @@
|
|
|
1
1
|
// src/config.ts
|
|
2
2
|
import { defineCrm } from "@odla-ai/crm";
|
|
3
3
|
|
|
4
|
+
// src/member.ts
|
|
5
|
+
import { assertFieldCondition, resolveFieldStates } from "@odla-ai/crm";
|
|
6
|
+
|
|
7
|
+
// src/application-id.ts
|
|
8
|
+
var APPLICATION_ID_DOMAIN = "odla-ai/chapter/application/v1:";
|
|
9
|
+
async function applicationIdForSubmission(submissionId) {
|
|
10
|
+
const input = new TextEncoder().encode(`${APPLICATION_ID_DOMAIN}${submissionId}`);
|
|
11
|
+
const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", input)).slice(0, 16);
|
|
12
|
+
bytes[6] = bytes[6] & 15 | 128;
|
|
13
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
14
|
+
const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
15
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/member.ts
|
|
19
|
+
var DEFAULT_REQUIRED = ["firstName", "lastName", "email", "referral", "whoYouAre", "message"];
|
|
20
|
+
var DEFAULT_OPTIONAL = ["referralName", "linkedin", "phone", "state"];
|
|
21
|
+
var SCHEMA_FIELD = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
22
|
+
function resolveApplication(a) {
|
|
23
|
+
const required = a?.required ?? DEFAULT_REQUIRED;
|
|
24
|
+
const optional = a?.optional ?? DEFAULT_OPTIONAL;
|
|
25
|
+
for (const [name, arr] of [["required", required], ["optional", optional]]) {
|
|
26
|
+
if (!Array.isArray(arr) || !arr.every((f) => typeof f === "string" && f !== "")) {
|
|
27
|
+
throw new Error(`defineChapter.application.${name}: must be an array of field-name strings`);
|
|
28
|
+
}
|
|
29
|
+
const unsafe = arr.find((field) => !SCHEMA_FIELD.test(field));
|
|
30
|
+
if (unsafe) {
|
|
31
|
+
throw new Error(`defineChapter.application.${name}: field "${unsafe}" must match ${SCHEMA_FIELD.source}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const duplicate = [...required, ...optional].find((field, index, fields) => fields.indexOf(field) !== index);
|
|
35
|
+
if (duplicate) {
|
|
36
|
+
throw new Error(`defineChapter.application: duplicate field "${duplicate}" across required/optional lists`);
|
|
37
|
+
}
|
|
38
|
+
if (a?.profileFields !== void 0 && (!Array.isArray(a.profileFields) || !a.profileFields.every((f) => typeof f === "string" && f !== ""))) {
|
|
39
|
+
throw new Error("defineChapter.application.profileFields: must be an array of field-name strings");
|
|
40
|
+
}
|
|
41
|
+
if (a?.crmFields !== void 0 && (!Array.isArray(a.crmFields) || !a.crmFields.every((f) => typeof f === "string" && f !== ""))) {
|
|
42
|
+
throw new Error("defineChapter.application.crmFields: must be an array of field-name strings");
|
|
43
|
+
}
|
|
44
|
+
const conditions = a?.conditions ?? {};
|
|
45
|
+
for (const [field, declared] of Object.entries(conditions)) {
|
|
46
|
+
for (const key of ["visibleWhen", "requiredWhen"]) {
|
|
47
|
+
const expression = declared?.[key];
|
|
48
|
+
if (expression === void 0) continue;
|
|
49
|
+
if (typeof expression !== "string" || !expression.trim()) {
|
|
50
|
+
throw new Error(`defineChapter.application.conditions.${field}.${key}: must be a non-empty CEL condition`);
|
|
51
|
+
}
|
|
52
|
+
assertFieldCondition(expression, `defineChapter.application.conditions.${field}.${key}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
required,
|
|
57
|
+
optional,
|
|
58
|
+
conditions,
|
|
59
|
+
maxLen: a?.maxLen ?? {},
|
|
60
|
+
defaultMaxLen: a?.defaultMaxLen ?? 2e3,
|
|
61
|
+
bodyCap: a?.bodyCap ?? 32768,
|
|
62
|
+
requireDisclaimerAck: a?.requireDisclaimerAck ?? true,
|
|
63
|
+
profileFields: a?.profileFields ?? [],
|
|
64
|
+
crmFields: a?.crmFields ?? [],
|
|
65
|
+
maxArrayLen: a?.maxArrayLen ?? 100,
|
|
66
|
+
validateEmail: a?.validateEmail ?? true
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
70
|
+
function isValidEmail(value) {
|
|
71
|
+
return typeof value === "string" && EMAIL_RE.test(value);
|
|
72
|
+
}
|
|
73
|
+
function clampArray(value, max) {
|
|
74
|
+
if (!Array.isArray(value)) return value;
|
|
75
|
+
return value.filter((x) => typeof x === "string" || typeof x === "number" || typeof x === "boolean").slice(0, max);
|
|
76
|
+
}
|
|
77
|
+
function hasDisclaimerAck(fields) {
|
|
78
|
+
return fields.disclaimerAck === true || fields.disclaimerAck === "true";
|
|
79
|
+
}
|
|
80
|
+
var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
|
|
81
|
+
function applicantProfile(chapter, fields) {
|
|
82
|
+
const app = chapter.application;
|
|
83
|
+
const allowed = (f) => app.profileFields.includes(f);
|
|
84
|
+
const profile = {};
|
|
85
|
+
for (const f of [...app.required, ...app.optional]) {
|
|
86
|
+
if (IDENTITY_FIELDS.has(f) || !allowed(f)) continue;
|
|
87
|
+
const v = fields[f];
|
|
88
|
+
if (typeof v === "string" && v.trim() !== "") profile[f] = v.trim();
|
|
89
|
+
}
|
|
90
|
+
if (fields.focus !== void 0 && allowed("focus")) profile.focus = clampArray(fields.focus, app.maxArrayLen);
|
|
91
|
+
return Object.keys(profile).length > 0 ? profile : void 0;
|
|
92
|
+
}
|
|
93
|
+
async function submitApplication(db, chapter, fields, opts) {
|
|
94
|
+
const app = chapter.application;
|
|
95
|
+
const fieldStates = resolveFieldStates(app.conditions ?? {}, fields, app.required);
|
|
96
|
+
for (const [f, state] of Object.entries(fieldStates)) {
|
|
97
|
+
if (!state.required) continue;
|
|
98
|
+
const v = fields[f];
|
|
99
|
+
if (typeof v !== "string" || v.trim() === "") return { ok: false, error: `${f} is required` };
|
|
100
|
+
}
|
|
101
|
+
for (const f of [...app.required, ...app.optional]) {
|
|
102
|
+
const v = fields[f];
|
|
103
|
+
const cap = app.maxLen[f] ?? app.defaultMaxLen;
|
|
104
|
+
if (typeof v === "string" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };
|
|
105
|
+
}
|
|
106
|
+
if (app.validateEmail && typeof fields.email === "string" && !isValidEmail(fields.email)) {
|
|
107
|
+
return { ok: false, error: "email must be a valid email address" };
|
|
108
|
+
}
|
|
109
|
+
const acked = hasDisclaimerAck(fields);
|
|
110
|
+
if (app.requireDisclaimerAck && !acked) {
|
|
111
|
+
return { ok: false, error: "disclaimerAck is required" };
|
|
112
|
+
}
|
|
113
|
+
const id2 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
|
|
114
|
+
const row = { id: id2, status: chapter.pipeline.initial, createdAt: opts.now };
|
|
115
|
+
for (const f of [...app.required, ...app.optional]) {
|
|
116
|
+
if (fieldStates[f]?.visible === false) continue;
|
|
117
|
+
if (typeof fields[f] === "string") row[f] = fields[f].trim();
|
|
118
|
+
}
|
|
119
|
+
if (fields.focus !== void 0) row.focus = clampArray(fields.focus, app.maxArrayLen);
|
|
120
|
+
if (opts.groupId) row.groupId = opts.groupId;
|
|
121
|
+
if (typeof fields.tierId === "string" && fields.tierId) {
|
|
122
|
+
if (opts.tierIds && !opts.tierIds.includes(fields.tierId)) {
|
|
123
|
+
return { ok: false, error: "tierId is not an offered tier" };
|
|
124
|
+
}
|
|
125
|
+
row.tierId = fields.tierId;
|
|
126
|
+
}
|
|
127
|
+
if (acked) row.disclaimerAckAt = opts.now;
|
|
128
|
+
const { duplicate } = await db.transact(
|
|
129
|
+
[{ t: "update", ns: "applications", id: id2, attrs: row }],
|
|
130
|
+
opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
|
|
131
|
+
);
|
|
132
|
+
return { ok: true, id: id2, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
|
|
133
|
+
}
|
|
134
|
+
function joinConfig(group, paymentsReady2, tiers2 = [], conditions = {}) {
|
|
135
|
+
return {
|
|
136
|
+
id: group.id,
|
|
137
|
+
name: group.name,
|
|
138
|
+
standardPriceCents: group.standardPriceCents ?? 0,
|
|
139
|
+
foundingDiscountCents: group.foundingDiscountCents ?? 0,
|
|
140
|
+
tiers: [...tiers2],
|
|
141
|
+
// The browser evaluates the same conditions the server enforces.
|
|
142
|
+
conditions,
|
|
143
|
+
disclaimerText: group.disclaimerText ?? "",
|
|
144
|
+
refundPolicyText: group.refundPolicyText ?? "",
|
|
145
|
+
trustCopy: group.trustCopy ?? "",
|
|
146
|
+
commitmentText: group.commitmentText ?? "",
|
|
147
|
+
normsText: group.normsText ?? "",
|
|
148
|
+
paymentsReady: paymentsReady2
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
4
152
|
// src/schema.ts
|
|
5
153
|
function attr(type, flags = {}) {
|
|
6
154
|
return {
|
|
@@ -75,6 +223,47 @@ var applications = {
|
|
|
75
223
|
canceled: attr("boolean", { optional: true })
|
|
76
224
|
}
|
|
77
225
|
};
|
|
226
|
+
var APPLICATION_INPUT_FIELDS = /* @__PURE__ */ new Set([
|
|
227
|
+
"firstName",
|
|
228
|
+
"lastName",
|
|
229
|
+
"email",
|
|
230
|
+
"referral",
|
|
231
|
+
"referralName",
|
|
232
|
+
"whoYouAre",
|
|
233
|
+
"focus",
|
|
234
|
+
"linkedin",
|
|
235
|
+
"message",
|
|
236
|
+
"phone",
|
|
237
|
+
"state"
|
|
238
|
+
]);
|
|
239
|
+
function applicationsFor(application) {
|
|
240
|
+
const attrs = {};
|
|
241
|
+
for (const [name, spec] of Object.entries(applications.attrs)) {
|
|
242
|
+
attrs[name] = {
|
|
243
|
+
...spec,
|
|
244
|
+
optional: APPLICATION_INPUT_FIELDS.has(name) ? true : spec.optional
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const apply = (field, optional) => {
|
|
248
|
+
const existing = Object.hasOwn(attrs, field) ? attrs[field] : void 0;
|
|
249
|
+
if (existing && !APPLICATION_INPUT_FIELDS.has(field)) {
|
|
250
|
+
throw new Error(`defineChapter.application: field "${field}" is reserved by the applications schema`);
|
|
251
|
+
}
|
|
252
|
+
if (existing && existing.type !== "string") {
|
|
253
|
+
throw new Error(`defineChapter.application: field "${field}" is not a configurable string field`);
|
|
254
|
+
}
|
|
255
|
+
const spec = existing ? { ...existing, optional } : attr("string", { optional });
|
|
256
|
+
Object.defineProperty(attrs, field, {
|
|
257
|
+
value: spec,
|
|
258
|
+
enumerable: true,
|
|
259
|
+
configurable: true,
|
|
260
|
+
writable: true
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
for (const field of application.required) apply(field, false);
|
|
264
|
+
for (const field of application.optional) apply(field, true);
|
|
265
|
+
return { attrs };
|
|
266
|
+
}
|
|
78
267
|
var groups = {
|
|
79
268
|
attrs: {
|
|
80
269
|
id: id(),
|
|
@@ -146,10 +335,10 @@ var emailLog = {
|
|
|
146
335
|
sentAt: attr("number", { indexed: true })
|
|
147
336
|
}
|
|
148
337
|
};
|
|
149
|
-
function chapterDb(mode, auth, includeNetworkNotes = false) {
|
|
338
|
+
function chapterDb(mode, auth, includeNetworkNotes = false, application = resolveApplication(void 0)) {
|
|
150
339
|
const entities = {};
|
|
151
340
|
if (mode === "chapter") {
|
|
152
|
-
entities.applications =
|
|
341
|
+
entities.applications = applicationsFor(application);
|
|
153
342
|
entities.groups = groups;
|
|
154
343
|
entities.tiers = tiers;
|
|
155
344
|
entities.meetings = meetings;
|
|
@@ -412,145 +601,6 @@ function canApprove(status, p) {
|
|
|
412
601
|
return p.approvableFrom.includes(status);
|
|
413
602
|
}
|
|
414
603
|
|
|
415
|
-
// src/member.ts
|
|
416
|
-
import { assertFieldCondition, resolveFieldStates } from "@odla-ai/crm";
|
|
417
|
-
|
|
418
|
-
// src/application-id.ts
|
|
419
|
-
var APPLICATION_ID_DOMAIN = "odla-ai/chapter/application/v1:";
|
|
420
|
-
async function applicationIdForSubmission(submissionId) {
|
|
421
|
-
const input = new TextEncoder().encode(`${APPLICATION_ID_DOMAIN}${submissionId}`);
|
|
422
|
-
const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", input)).slice(0, 16);
|
|
423
|
-
bytes[6] = bytes[6] & 15 | 128;
|
|
424
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
425
|
-
const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
426
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// src/member.ts
|
|
430
|
-
var DEFAULT_REQUIRED = ["firstName", "lastName", "email", "referral", "whoYouAre", "message"];
|
|
431
|
-
var DEFAULT_OPTIONAL = ["referralName", "linkedin", "phone", "state"];
|
|
432
|
-
function resolveApplication(a) {
|
|
433
|
-
const required = a?.required ?? DEFAULT_REQUIRED;
|
|
434
|
-
const optional = a?.optional ?? DEFAULT_OPTIONAL;
|
|
435
|
-
for (const [name, arr] of [["required", required], ["optional", optional]]) {
|
|
436
|
-
if (!Array.isArray(arr) || !arr.every((f) => typeof f === "string" && f !== "")) {
|
|
437
|
-
throw new Error(`defineChapter.application.${name}: must be an array of field-name strings`);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
if (a?.profileFields !== void 0 && (!Array.isArray(a.profileFields) || !a.profileFields.every((f) => typeof f === "string" && f !== ""))) {
|
|
441
|
-
throw new Error("defineChapter.application.profileFields: must be an array of field-name strings");
|
|
442
|
-
}
|
|
443
|
-
if (a?.crmFields !== void 0 && (!Array.isArray(a.crmFields) || !a.crmFields.every((f) => typeof f === "string" && f !== ""))) {
|
|
444
|
-
throw new Error("defineChapter.application.crmFields: must be an array of field-name strings");
|
|
445
|
-
}
|
|
446
|
-
const conditions = a?.conditions ?? {};
|
|
447
|
-
for (const [field, declared] of Object.entries(conditions)) {
|
|
448
|
-
for (const key of ["visibleWhen", "requiredWhen"]) {
|
|
449
|
-
const expression = declared?.[key];
|
|
450
|
-
if (expression === void 0) continue;
|
|
451
|
-
if (typeof expression !== "string" || !expression.trim()) {
|
|
452
|
-
throw new Error(`defineChapter.application.conditions.${field}.${key}: must be a non-empty CEL condition`);
|
|
453
|
-
}
|
|
454
|
-
assertFieldCondition(expression, `defineChapter.application.conditions.${field}.${key}`);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
return {
|
|
458
|
-
required,
|
|
459
|
-
optional,
|
|
460
|
-
conditions,
|
|
461
|
-
maxLen: a?.maxLen ?? {},
|
|
462
|
-
defaultMaxLen: a?.defaultMaxLen ?? 2e3,
|
|
463
|
-
bodyCap: a?.bodyCap ?? 32768,
|
|
464
|
-
requireDisclaimerAck: a?.requireDisclaimerAck ?? true,
|
|
465
|
-
profileFields: a?.profileFields ?? [],
|
|
466
|
-
crmFields: a?.crmFields ?? [],
|
|
467
|
-
maxArrayLen: a?.maxArrayLen ?? 100,
|
|
468
|
-
validateEmail: a?.validateEmail ?? true
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
472
|
-
function isValidEmail(value) {
|
|
473
|
-
return typeof value === "string" && EMAIL_RE.test(value);
|
|
474
|
-
}
|
|
475
|
-
function clampArray(value, max) {
|
|
476
|
-
if (!Array.isArray(value)) return value;
|
|
477
|
-
return value.filter((x) => typeof x === "string" || typeof x === "number" || typeof x === "boolean").slice(0, max);
|
|
478
|
-
}
|
|
479
|
-
function hasDisclaimerAck(fields) {
|
|
480
|
-
return fields.disclaimerAck === true || fields.disclaimerAck === "true";
|
|
481
|
-
}
|
|
482
|
-
var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
|
|
483
|
-
function applicantProfile(chapter, fields) {
|
|
484
|
-
const app = chapter.application;
|
|
485
|
-
const allowed = (f) => app.profileFields.includes(f);
|
|
486
|
-
const profile = {};
|
|
487
|
-
for (const f of [...app.required, ...app.optional]) {
|
|
488
|
-
if (IDENTITY_FIELDS.has(f) || !allowed(f)) continue;
|
|
489
|
-
const v = fields[f];
|
|
490
|
-
if (typeof v === "string" && v.trim() !== "") profile[f] = v.trim();
|
|
491
|
-
}
|
|
492
|
-
if (fields.focus !== void 0 && allowed("focus")) profile.focus = clampArray(fields.focus, app.maxArrayLen);
|
|
493
|
-
return Object.keys(profile).length > 0 ? profile : void 0;
|
|
494
|
-
}
|
|
495
|
-
async function submitApplication(db, chapter, fields, opts) {
|
|
496
|
-
const app = chapter.application;
|
|
497
|
-
const fieldStates = resolveFieldStates(app.conditions ?? {}, fields, app.required);
|
|
498
|
-
for (const [f, state] of Object.entries(fieldStates)) {
|
|
499
|
-
if (!state.required) continue;
|
|
500
|
-
const v = fields[f];
|
|
501
|
-
if (typeof v !== "string" || v.trim() === "") return { ok: false, error: `${f} is required` };
|
|
502
|
-
}
|
|
503
|
-
for (const f of [...app.required, ...app.optional]) {
|
|
504
|
-
const v = fields[f];
|
|
505
|
-
const cap = app.maxLen[f] ?? app.defaultMaxLen;
|
|
506
|
-
if (typeof v === "string" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };
|
|
507
|
-
}
|
|
508
|
-
if (app.validateEmail && typeof fields.email === "string" && !isValidEmail(fields.email)) {
|
|
509
|
-
return { ok: false, error: "email must be a valid email address" };
|
|
510
|
-
}
|
|
511
|
-
const acked = hasDisclaimerAck(fields);
|
|
512
|
-
if (app.requireDisclaimerAck && !acked) {
|
|
513
|
-
return { ok: false, error: "disclaimerAck is required" };
|
|
514
|
-
}
|
|
515
|
-
const id2 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
|
|
516
|
-
const row = { id: id2, status: chapter.pipeline.initial, createdAt: opts.now };
|
|
517
|
-
for (const f of [...app.required, ...app.optional]) {
|
|
518
|
-
if (fieldStates[f]?.visible === false) continue;
|
|
519
|
-
if (typeof fields[f] === "string") row[f] = fields[f].trim();
|
|
520
|
-
}
|
|
521
|
-
if (fields.focus !== void 0) row.focus = clampArray(fields.focus, app.maxArrayLen);
|
|
522
|
-
if (opts.groupId) row.groupId = opts.groupId;
|
|
523
|
-
if (typeof fields.tierId === "string" && fields.tierId) {
|
|
524
|
-
if (opts.tierIds && !opts.tierIds.includes(fields.tierId)) {
|
|
525
|
-
return { ok: false, error: "tierId is not an offered tier" };
|
|
526
|
-
}
|
|
527
|
-
row.tierId = fields.tierId;
|
|
528
|
-
}
|
|
529
|
-
if (acked) row.disclaimerAckAt = opts.now;
|
|
530
|
-
const { duplicate } = await db.transact(
|
|
531
|
-
[{ t: "update", ns: "applications", id: id2, attrs: row }],
|
|
532
|
-
opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
|
|
533
|
-
);
|
|
534
|
-
return { ok: true, id: id2, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
|
|
535
|
-
}
|
|
536
|
-
function joinConfig(group, paymentsReady2, tiers2 = [], conditions = {}) {
|
|
537
|
-
return {
|
|
538
|
-
id: group.id,
|
|
539
|
-
name: group.name,
|
|
540
|
-
standardPriceCents: group.standardPriceCents ?? 0,
|
|
541
|
-
foundingDiscountCents: group.foundingDiscountCents ?? 0,
|
|
542
|
-
tiers: [...tiers2],
|
|
543
|
-
// The browser evaluates the same conditions the server enforces.
|
|
544
|
-
conditions,
|
|
545
|
-
disclaimerText: group.disclaimerText ?? "",
|
|
546
|
-
refundPolicyText: group.refundPolicyText ?? "",
|
|
547
|
-
trustCopy: group.trustCopy ?? "",
|
|
548
|
-
commitmentText: group.commitmentText ?? "",
|
|
549
|
-
normsText: group.normsText ?? "",
|
|
550
|
-
paymentsReady: paymentsReady2
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
|
|
554
604
|
// src/copy-defaults-admin.ts
|
|
555
605
|
var DEFAULT_ADMIN_COPY = {
|
|
556
606
|
auth: {
|
|
@@ -1099,7 +1149,7 @@ function defineChapter(config) {
|
|
|
1099
1149
|
const copy = resolveChapterCopy(config.copy);
|
|
1100
1150
|
const network = resolveNetwork(config, crm);
|
|
1101
1151
|
const formation = resolveLeaderFormation(config.formation, crm);
|
|
1102
|
-
const { schema, rules } = chapterDb(mode, auth, network.targets.length > 0);
|
|
1152
|
+
const { schema, rules } = chapterDb(mode, auth, network.targets.length > 0, application);
|
|
1103
1153
|
const services = config.services ?? ["db", "calendar", "o11y"];
|
|
1104
1154
|
const account = config.account ?? "none";
|
|
1105
1155
|
if (account !== "invite" && account !== "create" && account !== "none") {
|
|
@@ -1557,53 +1607,10 @@ function emailGroupFrom(row) {
|
|
|
1557
1607
|
}
|
|
1558
1608
|
|
|
1559
1609
|
// src/payments.ts
|
|
1560
|
-
|
|
1561
|
-
const parts = {};
|
|
1562
|
-
for (const p of header.split(",")) {
|
|
1563
|
-
const [k, v] = p.split("=", 2);
|
|
1564
|
-
if (k && v !== void 0) parts[k] = v;
|
|
1565
|
-
}
|
|
1566
|
-
return { t: parts.t, v1: parts.v1 };
|
|
1567
|
-
}
|
|
1568
|
-
function toHex(buf) {
|
|
1569
|
-
return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1570
|
-
}
|
|
1571
|
-
function timingSafeEqual(a, b) {
|
|
1572
|
-
if (a.length !== b.length) return false;
|
|
1573
|
-
let diff = 0;
|
|
1574
|
-
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
1575
|
-
return diff === 0;
|
|
1576
|
-
}
|
|
1577
|
-
async function verifyStripeSignature(payload, header, secret, opts = {}) {
|
|
1578
|
-
const { t, v1 } = parseSigHeader(header);
|
|
1579
|
-
if (!t || !v1) return false;
|
|
1580
|
-
const ts = Number(t);
|
|
1581
|
-
if (!Number.isFinite(ts)) return false;
|
|
1582
|
-
const nowSec = (opts.now ?? Date.now()) / 1e3;
|
|
1583
|
-
const tolerance = opts.toleranceSec ?? 300;
|
|
1584
|
-
if (Math.abs(nowSec - ts) > tolerance) return false;
|
|
1585
|
-
const enc = new TextEncoder();
|
|
1586
|
-
const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
1587
|
-
const mac = await crypto.subtle.sign("HMAC", key, enc.encode(`${t}.${payload}`));
|
|
1588
|
-
return timingSafeEqual(toHex(mac), v1);
|
|
1589
|
-
}
|
|
1610
|
+
import { stripeForm, verifyStripeSignature } from "@odla-ai/stripe";
|
|
1590
1611
|
function paymentsReady(group, hasSecretKey) {
|
|
1591
1612
|
return Boolean(group.stripePublishableKey && group.stripePriceId && hasSecretKey);
|
|
1592
1613
|
}
|
|
1593
|
-
function stripeForm(params) {
|
|
1594
|
-
const out = new URLSearchParams();
|
|
1595
|
-
for (const [k, v] of Object.entries(params)) {
|
|
1596
|
-
if (v === void 0 || v === null) continue;
|
|
1597
|
-
if (typeof v === "object") {
|
|
1598
|
-
for (const [k2, v2] of Object.entries(v)) {
|
|
1599
|
-
if (v2 !== void 0 && v2 !== null) out.append(`${k}[${k2}]`, String(v2));
|
|
1600
|
-
}
|
|
1601
|
-
} else {
|
|
1602
|
-
out.append(k, String(v));
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
return out.toString();
|
|
1606
|
-
}
|
|
1607
1614
|
function subscriptionIdempotencyKey(applicationId) {
|
|
1608
1615
|
return `sub:${applicationId}`;
|
|
1609
1616
|
}
|
|
@@ -1992,18 +1999,15 @@ function subAnnualCents(sub) {
|
|
|
1992
1999
|
}
|
|
1993
2000
|
|
|
1994
2001
|
// src/payments-stripe.ts
|
|
2002
|
+
import { stripeRequest, verifyStripeSignature as verifyStripeSignature2 } from "@odla-ai/stripe";
|
|
1995
2003
|
async function stripeCall(sk, method, path, params, idempotencyKey) {
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
}
|
|
2004
|
-
const res = await fetch(`https://api.stripe.com${path}${qs}`, init);
|
|
2005
|
-
const body = await res.json().catch(() => ({}));
|
|
2006
|
-
return { ok: res.ok, status: res.status, body };
|
|
2004
|
+
return stripeRequest(
|
|
2005
|
+
{ secretKey: sk },
|
|
2006
|
+
method,
|
|
2007
|
+
path,
|
|
2008
|
+
params,
|
|
2009
|
+
idempotencyKey ? { idempotencyKey } : {}
|
|
2010
|
+
);
|
|
2007
2011
|
}
|
|
2008
2012
|
|
|
2009
2013
|
// src/clerk.ts
|