@odla-ai/chapter 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -14
- package/dist/index.cjs +55 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -2
- package/dist/index.d.ts +39 -2
- package/dist/index.js +55 -13
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +99 -27
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +17 -0
- package/dist/worker/index.d.ts +17 -0
- package/dist/worker/index.js +99 -27
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -5
package/dist/index.d.cts
CHANGED
|
@@ -215,9 +215,24 @@ interface ChapterConfig {
|
|
|
215
215
|
* account is ready), `"none"` skips it. Any of these needs a `clerk_secret_key`
|
|
216
216
|
* vault secret to act. */
|
|
217
217
|
account?: AccountModel;
|
|
218
|
+
/** WHEN lifecycle email fires. Addressing and content live on the group row
|
|
219
|
+
* (owner-editable at runtime); this is the trigger, which is a build-time
|
|
220
|
+
* decision. See {@link ChapterSends}. */
|
|
221
|
+
sends?: ChapterSends;
|
|
218
222
|
}
|
|
219
223
|
/** Apply-time Clerk account provisioning model. */
|
|
220
224
|
type AccountModel = "invite" | "create" | "none";
|
|
225
|
+
/** When the admin notification fires: on application `submit` (default), on the
|
|
226
|
+
* first successful `payment`, or `never` (the site drives it itself). */
|
|
227
|
+
type AdminNotificationTrigger = "submit" | "payment" | "never";
|
|
228
|
+
/** Send-policy config: the trigger for each lifecycle email chapter owns. */
|
|
229
|
+
interface ChapterSends {
|
|
230
|
+
adminNotification?: AdminNotificationTrigger;
|
|
231
|
+
}
|
|
232
|
+
/** Resolved send policy (every trigger present). */
|
|
233
|
+
interface ResolvedSends {
|
|
234
|
+
adminNotification: AdminNotificationTrigger;
|
|
235
|
+
}
|
|
221
236
|
/** The resolved engine `defineChapter()` returns. */
|
|
222
237
|
interface Chapter {
|
|
223
238
|
config: ChapterConfig;
|
|
@@ -239,6 +254,8 @@ interface Chapter {
|
|
|
239
254
|
services: readonly string[];
|
|
240
255
|
/** Resolved apply-time account provisioning model (default `"invite"`). */
|
|
241
256
|
account: AccountModel;
|
|
257
|
+
/** Resolved send policy — when each lifecycle email fires. */
|
|
258
|
+
sends: ResolvedSends;
|
|
242
259
|
/** The seed `groups` row derived from config (chapter mode), else `null`. */
|
|
243
260
|
groupSeed(): Record<string, unknown> | null;
|
|
244
261
|
}
|
|
@@ -708,6 +725,8 @@ interface ClerkResult {
|
|
|
708
725
|
ok: boolean;
|
|
709
726
|
status: number;
|
|
710
727
|
existed?: boolean;
|
|
728
|
+
/** Set when an `existed` heal also refreshed the account's public_metadata. */
|
|
729
|
+
refreshed?: boolean;
|
|
711
730
|
}
|
|
712
731
|
/** Inputs for a Clerk invitation. */
|
|
713
732
|
interface ClerkInviteInput {
|
|
@@ -746,7 +765,9 @@ declare function clerkUserRequest(input: ClerkUserInput): {
|
|
|
746
765
|
};
|
|
747
766
|
/** Create the applicant's Clerk account server-side. A repeat for an email that
|
|
748
767
|
* already has an account heals to `{ ok: true, existed: true }` — the account
|
|
749
|
-
* exists, which is the state apply-time provisioning wanted
|
|
768
|
+
* exists, which is the state apply-time provisioning wanted — and, when
|
|
769
|
+
* `publicMetadata` was supplied, refreshes it on the existing account so a
|
|
770
|
+
* re-application repairs a previously missed create (`refreshed: true`). */
|
|
750
771
|
declare function createClerkUser(secretKey: string, input: ClerkUserInput, fetchImpl?: typeof fetch): Promise<ClerkResult>;
|
|
751
772
|
|
|
752
773
|
/** An application row, as far as the session cares about it. */
|
|
@@ -888,6 +909,22 @@ declare const SCHEDULING_DEFAULTS: ResolvedScheduling;
|
|
|
888
909
|
* `windowDays` is capped at 62 because Google FreeBusy is.
|
|
889
910
|
*/
|
|
890
911
|
declare function resolveScheduling(config?: ChapterScheduling): ResolvedScheduling;
|
|
912
|
+
/** Validation messages keyed by form field. */
|
|
913
|
+
type SchedulingErrors = Record<string, string>;
|
|
914
|
+
/**
|
|
915
|
+
* Validate a scheduling config field by field, collecting owner-readable messages
|
|
916
|
+
* rather than throwing on the first problem. An admin settings form needs to say
|
|
917
|
+
* *which* field is wrong and why — "pick at least one day" beats a stack trace —
|
|
918
|
+
* so the admin route returns these directly. {@link resolveScheduling} is the
|
|
919
|
+
* throwing wrapper for internal/config-time use.
|
|
920
|
+
*/
|
|
921
|
+
declare function validateScheduling(config?: ChapterScheduling): {
|
|
922
|
+
ok: true;
|
|
923
|
+
value: ResolvedScheduling;
|
|
924
|
+
} | {
|
|
925
|
+
ok: false;
|
|
926
|
+
errors: SchedulingErrors;
|
|
927
|
+
};
|
|
891
928
|
/** Statuses a member may book/reschedule from (early pipeline only). */
|
|
892
929
|
/** The availability window: `[now, now + windowDays]` in epoch ms. */
|
|
893
930
|
declare function slotWindow(now: number, windowDays: number): {
|
|
@@ -971,4 +1008,4 @@ type ApplicationBookingPatch = {
|
|
|
971
1008
|
* already there (never backward). */
|
|
972
1009
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
973
1010
|
|
|
974
|
-
export { type AccountModel, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, verifyStripeSignature, webhookMutationId };
|
|
1011
|
+
export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, validateScheduling, verifyStripeSignature, webhookMutationId };
|
package/dist/index.d.ts
CHANGED
|
@@ -215,9 +215,24 @@ interface ChapterConfig {
|
|
|
215
215
|
* account is ready), `"none"` skips it. Any of these needs a `clerk_secret_key`
|
|
216
216
|
* vault secret to act. */
|
|
217
217
|
account?: AccountModel;
|
|
218
|
+
/** WHEN lifecycle email fires. Addressing and content live on the group row
|
|
219
|
+
* (owner-editable at runtime); this is the trigger, which is a build-time
|
|
220
|
+
* decision. See {@link ChapterSends}. */
|
|
221
|
+
sends?: ChapterSends;
|
|
218
222
|
}
|
|
219
223
|
/** Apply-time Clerk account provisioning model. */
|
|
220
224
|
type AccountModel = "invite" | "create" | "none";
|
|
225
|
+
/** When the admin notification fires: on application `submit` (default), on the
|
|
226
|
+
* first successful `payment`, or `never` (the site drives it itself). */
|
|
227
|
+
type AdminNotificationTrigger = "submit" | "payment" | "never";
|
|
228
|
+
/** Send-policy config: the trigger for each lifecycle email chapter owns. */
|
|
229
|
+
interface ChapterSends {
|
|
230
|
+
adminNotification?: AdminNotificationTrigger;
|
|
231
|
+
}
|
|
232
|
+
/** Resolved send policy (every trigger present). */
|
|
233
|
+
interface ResolvedSends {
|
|
234
|
+
adminNotification: AdminNotificationTrigger;
|
|
235
|
+
}
|
|
221
236
|
/** The resolved engine `defineChapter()` returns. */
|
|
222
237
|
interface Chapter {
|
|
223
238
|
config: ChapterConfig;
|
|
@@ -239,6 +254,8 @@ interface Chapter {
|
|
|
239
254
|
services: readonly string[];
|
|
240
255
|
/** Resolved apply-time account provisioning model (default `"invite"`). */
|
|
241
256
|
account: AccountModel;
|
|
257
|
+
/** Resolved send policy — when each lifecycle email fires. */
|
|
258
|
+
sends: ResolvedSends;
|
|
242
259
|
/** The seed `groups` row derived from config (chapter mode), else `null`. */
|
|
243
260
|
groupSeed(): Record<string, unknown> | null;
|
|
244
261
|
}
|
|
@@ -708,6 +725,8 @@ interface ClerkResult {
|
|
|
708
725
|
ok: boolean;
|
|
709
726
|
status: number;
|
|
710
727
|
existed?: boolean;
|
|
728
|
+
/** Set when an `existed` heal also refreshed the account's public_metadata. */
|
|
729
|
+
refreshed?: boolean;
|
|
711
730
|
}
|
|
712
731
|
/** Inputs for a Clerk invitation. */
|
|
713
732
|
interface ClerkInviteInput {
|
|
@@ -746,7 +765,9 @@ declare function clerkUserRequest(input: ClerkUserInput): {
|
|
|
746
765
|
};
|
|
747
766
|
/** Create the applicant's Clerk account server-side. A repeat for an email that
|
|
748
767
|
* already has an account heals to `{ ok: true, existed: true }` — the account
|
|
749
|
-
* exists, which is the state apply-time provisioning wanted
|
|
768
|
+
* exists, which is the state apply-time provisioning wanted — and, when
|
|
769
|
+
* `publicMetadata` was supplied, refreshes it on the existing account so a
|
|
770
|
+
* re-application repairs a previously missed create (`refreshed: true`). */
|
|
750
771
|
declare function createClerkUser(secretKey: string, input: ClerkUserInput, fetchImpl?: typeof fetch): Promise<ClerkResult>;
|
|
751
772
|
|
|
752
773
|
/** An application row, as far as the session cares about it. */
|
|
@@ -888,6 +909,22 @@ declare const SCHEDULING_DEFAULTS: ResolvedScheduling;
|
|
|
888
909
|
* `windowDays` is capped at 62 because Google FreeBusy is.
|
|
889
910
|
*/
|
|
890
911
|
declare function resolveScheduling(config?: ChapterScheduling): ResolvedScheduling;
|
|
912
|
+
/** Validation messages keyed by form field. */
|
|
913
|
+
type SchedulingErrors = Record<string, string>;
|
|
914
|
+
/**
|
|
915
|
+
* Validate a scheduling config field by field, collecting owner-readable messages
|
|
916
|
+
* rather than throwing on the first problem. An admin settings form needs to say
|
|
917
|
+
* *which* field is wrong and why — "pick at least one day" beats a stack trace —
|
|
918
|
+
* so the admin route returns these directly. {@link resolveScheduling} is the
|
|
919
|
+
* throwing wrapper for internal/config-time use.
|
|
920
|
+
*/
|
|
921
|
+
declare function validateScheduling(config?: ChapterScheduling): {
|
|
922
|
+
ok: true;
|
|
923
|
+
value: ResolvedScheduling;
|
|
924
|
+
} | {
|
|
925
|
+
ok: false;
|
|
926
|
+
errors: SchedulingErrors;
|
|
927
|
+
};
|
|
891
928
|
/** Statuses a member may book/reschedule from (early pipeline only). */
|
|
892
929
|
/** The availability window: `[now, now + windowDays]` in epoch ms. */
|
|
893
930
|
declare function slotWindow(now: number, windowDays: number): {
|
|
@@ -971,4 +1008,4 @@ type ApplicationBookingPatch = {
|
|
|
971
1008
|
* already there (never backward). */
|
|
972
1009
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
973
1010
|
|
|
974
|
-
export { type AccountModel, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, verifyStripeSignature, webhookMutationId };
|
|
1011
|
+
export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretStore, type SessionUser, type SharedPerson, type StripeEvent, type SubmitResult, type WebhookEvent, applicationBookingUpdate, applicationSummary, bookingDecision, brandTokens, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clerkInviteRequest, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, normalizeWebhookEvent, paymentsReady, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, slotWindow, stageIndex, stripeForm, submitApplication, subscriptionIdempotencyKey, validateScheduling, verifyStripeSignature, webhookMutationId };
|
package/dist/index.js
CHANGED
|
@@ -474,6 +474,13 @@ function defineChapter(config) {
|
|
|
474
474
|
if (account !== "invite" && account !== "create" && account !== "none") {
|
|
475
475
|
throw new Error(`defineChapter.account: must be "invite", "create", or "none" \u2014 got "${String(account)}"`);
|
|
476
476
|
}
|
|
477
|
+
const adminNotification = config.sends?.adminNotification ?? "submit";
|
|
478
|
+
if (adminNotification !== "submit" && adminNotification !== "payment" && adminNotification !== "never") {
|
|
479
|
+
throw new Error(
|
|
480
|
+
`defineChapter.sends.adminNotification: must be "submit", "payment", or "never" \u2014 got "${String(adminNotification)}"`
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
const sends = { adminNotification };
|
|
477
484
|
const chapter = {
|
|
478
485
|
config,
|
|
479
486
|
id: id2,
|
|
@@ -487,6 +494,7 @@ function defineChapter(config) {
|
|
|
487
494
|
rules,
|
|
488
495
|
services,
|
|
489
496
|
account,
|
|
497
|
+
sends,
|
|
490
498
|
groupSeed: () => mode === "chapter" ? buildGroupSeed(config) : null
|
|
491
499
|
};
|
|
492
500
|
if (config.url !== void 0) chapter.url = config.url;
|
|
@@ -797,6 +805,20 @@ function clerkUserRequest(input) {
|
|
|
797
805
|
}
|
|
798
806
|
};
|
|
799
807
|
}
|
|
808
|
+
async function refreshUserMetadata(secretKey, email, publicMetadata, fetchImpl) {
|
|
809
|
+
const auth = { authorization: `Bearer ${secretKey}` };
|
|
810
|
+
const found = await fetchImpl(`https://api.clerk.com/v1/users?email_address=${encodeURIComponent(email)}&limit=1`, { headers: auth });
|
|
811
|
+
if (!found.ok) return false;
|
|
812
|
+
const users = await found.json().catch(() => null);
|
|
813
|
+
const id2 = Array.isArray(users) && typeof users[0]?.id === "string" ? users[0].id : void 0;
|
|
814
|
+
if (!id2) return false;
|
|
815
|
+
const patched = await fetchImpl(`https://api.clerk.com/v1/users/${id2}/metadata`, {
|
|
816
|
+
method: "PATCH",
|
|
817
|
+
headers: { ...auth, "content-type": "application/json" },
|
|
818
|
+
body: JSON.stringify({ public_metadata: publicMetadata })
|
|
819
|
+
});
|
|
820
|
+
return patched.ok;
|
|
821
|
+
}
|
|
800
822
|
async function createClerkUser(secretKey, input, fetchImpl = fetch) {
|
|
801
823
|
const { path, body } = clerkUserRequest(input);
|
|
802
824
|
const res = await fetchImpl(`https://api.clerk.com${path}`, {
|
|
@@ -804,7 +826,11 @@ async function createClerkUser(secretKey, input, fetchImpl = fetch) {
|
|
|
804
826
|
headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
|
|
805
827
|
body: JSON.stringify(body)
|
|
806
828
|
});
|
|
807
|
-
|
|
829
|
+
if (res.ok) return { ok: true, status: res.status };
|
|
830
|
+
const healed = heal(res.status);
|
|
831
|
+
if (!healed.existed || !input.publicMetadata) return healed;
|
|
832
|
+
const refreshed = await refreshUserMetadata(secretKey, input.email, input.publicMetadata, fetchImpl).catch(() => false);
|
|
833
|
+
return { ...healed, refreshed };
|
|
808
834
|
}
|
|
809
835
|
|
|
810
836
|
// src/session.ts
|
|
@@ -923,6 +949,12 @@ function isValidTimeZone(tz) {
|
|
|
923
949
|
}
|
|
924
950
|
}
|
|
925
951
|
function resolveScheduling(config) {
|
|
952
|
+
const result = validateScheduling(config);
|
|
953
|
+
if (result.ok) return result.value;
|
|
954
|
+
const detail = Object.entries(result.errors).map(([field, message]) => `${field}: ${message}`).join(" ");
|
|
955
|
+
throw new Error(`scheduling: ${detail}`);
|
|
956
|
+
}
|
|
957
|
+
function validateScheduling(config) {
|
|
926
958
|
const d = config ?? {};
|
|
927
959
|
const c = {
|
|
928
960
|
slotMinutes: d.slotMinutes ?? SCHEDULING_DEFAULTS.slotMinutes,
|
|
@@ -934,20 +966,29 @@ function resolveScheduling(config) {
|
|
|
934
966
|
windowDays: d.windowDays ?? SCHEDULING_DEFAULTS.windowDays,
|
|
935
967
|
summaryTemplate: d.summaryTemplate ?? SCHEDULING_DEFAULTS.summaryTemplate
|
|
936
968
|
};
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
if (!(c.windowDays >= 1 && c.windowDays <= 62))
|
|
942
|
-
|
|
943
|
-
|
|
969
|
+
const errors = {};
|
|
970
|
+
if (!(c.slotMinutes >= 15 && c.slotMinutes <= 240)) {
|
|
971
|
+
errors.slotMinutes = "Slot length must be between 15 and 240 minutes.";
|
|
972
|
+
}
|
|
973
|
+
if (!(c.windowDays >= 1 && c.windowDays <= 62)) {
|
|
974
|
+
errors.windowDays = "Booking window must be between 1 and 62 days (the calendar caps look-ahead at 62).";
|
|
975
|
+
}
|
|
976
|
+
if (!(c.minNoticeHours >= 0 && c.minNoticeHours <= 336)) {
|
|
977
|
+
errors.minNoticeHours = "Minimum notice must be between 0 and 336 hours.";
|
|
978
|
+
}
|
|
979
|
+
if (!(c.startHour >= 0 && c.startHour < c.endHour && c.endHour <= 24)) {
|
|
980
|
+
errors.hours = "Hours must satisfy 0 \u2264 start < end \u2264 24.";
|
|
981
|
+
}
|
|
944
982
|
const days = [...c.days];
|
|
945
|
-
if (!days.length
|
|
946
|
-
|
|
983
|
+
if (!days.length) errors.days = "Pick at least one day.";
|
|
984
|
+
else if (!days.every((n) => Number.isInteger(n) && n >= 0 && n <= 6)) {
|
|
985
|
+
errors.days = "Days must be weekday numbers, 0 (Sunday) through 6 (Saturday).";
|
|
986
|
+
}
|
|
987
|
+
if (typeof c.timezone !== "string" || !isValidTimeZone(c.timezone)) {
|
|
988
|
+
errors.timezone = `"${String(c.timezone)}" is not a valid IANA timezone (for example "America/Los_Angeles").`;
|
|
947
989
|
}
|
|
948
|
-
if (typeof c.
|
|
949
|
-
|
|
950
|
-
return { ...c, days };
|
|
990
|
+
if (typeof c.summaryTemplate !== "string") errors.summaryTemplate = "Calendar summary template must be text.";
|
|
991
|
+
return Object.keys(errors).length > 0 ? { ok: false, errors } : { ok: true, value: { ...c, days } };
|
|
951
992
|
}
|
|
952
993
|
function slotWindow(now, windowDays) {
|
|
953
994
|
return { from: now, to: now + windowDays * 864e5 };
|
|
@@ -1052,6 +1093,7 @@ export {
|
|
|
1052
1093
|
stripeForm,
|
|
1053
1094
|
submitApplication,
|
|
1054
1095
|
subscriptionIdempotencyKey,
|
|
1096
|
+
validateScheduling,
|
|
1055
1097
|
verifyStripeSignature,
|
|
1056
1098
|
webhookMutationId
|
|
1057
1099
|
};
|