@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/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 + the CRM slot projection; defaults to the reference form.
483
- * The `applications` schema attrs stay fixed; this is
484
- * validation config, not schema generation. */
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, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, tierIsFree, tierPayable, updateClerkUserMetadata, updateClerkUserMetadataByEmail, validateScheduling, verifyStripeSignature, webhookMutationId };
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 + the CRM slot projection; defaults to the reference form.
483
- * The `applications` schema attrs stay fixed; this is
484
- * validation config, not schema generation. */
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, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, tierIsFree, tierPayable, updateClerkUserMetadata, updateClerkUserMetadataByEmail, validateScheduling, verifyStripeSignature, webhookMutationId };
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 = 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
- function parseSigHeader(header) {
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
- const qs = method === "GET" && params ? `?${stripeForm(params)}` : "";
1997
- const headers = { authorization: `Bearer ${sk}` };
1998
- if (idempotencyKey) headers["idempotency-key"] = idempotencyKey;
1999
- const init = { method, headers };
2000
- if (method === "POST" && params) {
2001
- headers["content-type"] = "application/x-www-form-urlencoded";
2002
- init.body = stripeForm(params);
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