@odla-ai/chapter 0.24.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -32
- package/dist/{copy-context-Dp5hvj5W.d.ts → copy-context-DI21CYQ3.d.ts} +3 -4
- package/dist/index.cjs +51 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -24
- package/dist/index.d.ts +50 -24
- package/dist/index.js +51 -14
- 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 +211 -22
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +3 -4
- package/dist/worker/index.d.ts +3 -4
- package/dist/worker/index.js +211 -22
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/runbooks/adopt-existing.md +93 -21
- package/runbooks/greenfield.md +31 -31
package/dist/index.d.cts
CHANGED
|
@@ -327,10 +327,9 @@ interface ChapterApplication {
|
|
|
327
327
|
* writing a row with no consent record. Default `true`. Set `false`
|
|
328
328
|
* deliberately only when the site renders no consent control. */
|
|
329
329
|
requireDisclaimerAck?: boolean;
|
|
330
|
-
/** Allowlist of fields
|
|
331
|
-
* `
|
|
332
|
-
*
|
|
333
|
-
* fields the browser may read. */
|
|
330
|
+
/** Allowlist of application fields mirrored into the Clerk account's
|
|
331
|
+
* backend-only `private_metadata.profile`. Default `[]`. Use this for small
|
|
332
|
+
* account/admin signals; the application row and CRM remain canonical. */
|
|
334
333
|
profileFields?: readonly string[];
|
|
335
334
|
/** Extra application fields carried into the one-way CRM projection, on top of
|
|
336
335
|
* the built-in identity/contact set. Each MUST be declared on your crm person
|
|
@@ -877,11 +876,10 @@ declare function clampArray(value: unknown, max: number): unknown;
|
|
|
877
876
|
* the boolean an API client sends and the string a plain HTML form posts. */
|
|
878
877
|
declare function hasDisclaimerAck(fields: Record<string, unknown>): boolean;
|
|
879
878
|
/**
|
|
880
|
-
* The applicant profile written to the Clerk account's
|
|
881
|
-
* `
|
|
882
|
-
* `focus` (clamped), but ONLY those in `application.profileFields
|
|
883
|
-
*
|
|
884
|
-
* config, so a site's own field names project without this package knowing
|
|
879
|
+
* The applicant profile written to the Clerk account's backend-only
|
|
880
|
+
* `private_metadata.profile`. Projects each configured non-identity field, plus
|
|
881
|
+
* `focus` (clamped), but ONLY those in `application.profileFields`. Derived
|
|
882
|
+
* from config, so a site's own field names project without this package knowing
|
|
885
883
|
* them. Pure; returns `undefined` when there is nothing to write.
|
|
886
884
|
*/
|
|
887
885
|
declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
@@ -1097,7 +1095,7 @@ interface ClerkResult {
|
|
|
1097
1095
|
ok: boolean;
|
|
1098
1096
|
status: number;
|
|
1099
1097
|
existed?: boolean;
|
|
1100
|
-
/** Set when an `existed` heal also refreshed the account's
|
|
1098
|
+
/** Set when an `existed` heal also refreshed the account's metadata. */
|
|
1101
1099
|
refreshed?: boolean;
|
|
1102
1100
|
}
|
|
1103
1101
|
/** Inputs for a Clerk invitation. */
|
|
@@ -1105,8 +1103,9 @@ interface ClerkInviteInput {
|
|
|
1105
1103
|
email: string;
|
|
1106
1104
|
/** Where the accept-invitation link lands (usually the member area). */
|
|
1107
1105
|
redirectUrl?: string;
|
|
1108
|
-
/** Written to the invitation's `public_metadata
|
|
1109
|
-
*
|
|
1106
|
+
/** Written to the invitation's `public_metadata`. Clerk application
|
|
1107
|
+
* invitations cannot carry private metadata, so never put applicant profile
|
|
1108
|
+
* fields here. Chapter completes that backend-only write after acceptance. */
|
|
1110
1109
|
publicMetadata?: Record<string, unknown>;
|
|
1111
1110
|
}
|
|
1112
1111
|
/** Build the Clerk Backend API invitation request (path + JSON body). Pure, so
|
|
@@ -1124,9 +1123,14 @@ interface ClerkUserInput {
|
|
|
1124
1123
|
email: string;
|
|
1125
1124
|
firstName?: string;
|
|
1126
1125
|
lastName?: string;
|
|
1127
|
-
/**
|
|
1128
|
-
* (role, tier, whatever the member area reads). */
|
|
1126
|
+
/** Backend-authored, browser-readable claims such as `role`. */
|
|
1129
1127
|
publicMetadata?: Record<string, unknown>;
|
|
1128
|
+
/** Backend-only account signals and profile data. */
|
|
1129
|
+
privateMetadata?: Record<string, unknown>;
|
|
1130
|
+
/** Optional metadata merge used only when create returns 422 for an existing
|
|
1131
|
+
* account. This lets migrations remove legacy keys without putting tombstone
|
|
1132
|
+
* values on a newly-created user. Defaults to the create metadata. */
|
|
1133
|
+
repairMetadata?: ClerkMetadataInput;
|
|
1130
1134
|
}
|
|
1131
1135
|
/** Build the Clerk Backend API user-create request. The account is created
|
|
1132
1136
|
* passwordless (the member signs in via the site's Clerk flow), so join step 3
|
|
@@ -1135,21 +1139,41 @@ declare function clerkUserRequest(input: ClerkUserInput): {
|
|
|
1135
1139
|
path: string;
|
|
1136
1140
|
body: Record<string, unknown>;
|
|
1137
1141
|
};
|
|
1142
|
+
/** One merge-PATCH to the metadata fields Clerk exposes on its Backend API. */
|
|
1143
|
+
interface ClerkMetadataInput {
|
|
1144
|
+
publicMetadata?: Record<string, unknown>;
|
|
1145
|
+
privateMetadata?: Record<string, unknown>;
|
|
1146
|
+
}
|
|
1147
|
+
/** Merge metadata into an existing Clerk user. Nested `null` values remove old
|
|
1148
|
+
* keys, which Chapter uses to migrate its former public profile into private
|
|
1149
|
+
* metadata without disturbing the public `role` claim. */
|
|
1150
|
+
declare function updateClerkUserMetadata(secretKey: string, userId: string, input: ClerkMetadataInput, fetchImpl?: typeof fetch): Promise<boolean>;
|
|
1151
|
+
/** Look a Clerk account up by email and merge its metadata.
|
|
1152
|
+
*
|
|
1153
|
+
* Returns `false` when no matching user can be resolved or Clerk rejects the
|
|
1154
|
+
* lookup or update. Chapter uses this for create-healing and invitation
|
|
1155
|
+
* adoption so a repeated application repairs the private account snapshot.
|
|
1156
|
+
*/
|
|
1157
|
+
declare function updateClerkUserMetadataByEmail(secretKey: string, email: string, input: ClerkMetadataInput, fetchImpl?: typeof fetch): Promise<boolean>;
|
|
1138
1158
|
/** Create the applicant's Clerk account server-side. A repeat for an email that
|
|
1139
1159
|
* already has an account heals to `{ ok: true, existed: true }` — the account
|
|
1140
|
-
* exists, which is the state apply-time provisioning wanted — and
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1160
|
+
* exists, which is the state apply-time provisioning wanted — and refreshes
|
|
1161
|
+
* supplied public/private metadata on the existing account so a re-application
|
|
1162
|
+
* repairs a previously missed create (`refreshed: true`). */
|
|
1143
1163
|
declare function createClerkUser(secretKey: string, input: ClerkUserInput, fetchImpl?: typeof fetch): Promise<ClerkResult>;
|
|
1144
1164
|
|
|
1145
1165
|
/** A Clerk user as chapter's role layer sees it. `role` defaults to the lowest
|
|
1146
|
-
* rung when `public_metadata` carries none
|
|
1147
|
-
*
|
|
1166
|
+
* rung when `public_metadata` carries none. Raw public/private metadata is
|
|
1167
|
+
* returned only from this backend helper; `profile` and `applicationId` are the
|
|
1168
|
+
* normalized Chapter-owned private signals. */
|
|
1148
1169
|
interface ClerkUserRecord {
|
|
1149
1170
|
id: string;
|
|
1150
1171
|
email?: string;
|
|
1151
1172
|
role: string;
|
|
1152
1173
|
publicMetadata: Record<string, unknown>;
|
|
1174
|
+
privateMetadata: Record<string, unknown>;
|
|
1175
|
+
profile: Record<string, unknown>;
|
|
1176
|
+
applicationId?: string;
|
|
1153
1177
|
}
|
|
1154
1178
|
/** Look a Clerk user up by email. `null` when no such user — or when the lookup
|
|
1155
1179
|
* fails (a role gate treats an unresolvable user as absent, matching the site's
|
|
@@ -1164,8 +1188,9 @@ declare function clerkGetUser(secretKey: string, id: string, fetchImpl?: typeof
|
|
|
1164
1188
|
* until a short page. A page fetch that fails THROWS rather than returning a
|
|
1165
1189
|
* partial list, so the caller never mistakes a truncated roster for the whole. */
|
|
1166
1190
|
declare function clerkListUsers(secretKey: string, fetchImpl?: typeof fetch): Promise<ClerkUserRecord[]>;
|
|
1167
|
-
/** Write a user's role: a MERGE-PATCH of only `{ role }` on `public_metadata
|
|
1168
|
-
*
|
|
1191
|
+
/** Write a user's role: a MERGE-PATCH of only `{ role }` on `public_metadata`.
|
|
1192
|
+
* The backend-only profile lives in `private_metadata`, so the two signals
|
|
1193
|
+
* cannot overwrite each other. Returns whether the role write stuck. */
|
|
1169
1194
|
declare function clerkSetRole(secretKey: string, id: string, role: string, fetchImpl?: typeof fetch): Promise<boolean>;
|
|
1170
1195
|
|
|
1171
1196
|
/** Which auth mode a secret is required for: `"client"` (publishable key only) or
|
|
@@ -1231,8 +1256,9 @@ interface IntegrationDescriptor {
|
|
|
1231
1256
|
* the one public `setting` (the `pk_*` publishable key, served to the SPA), the
|
|
1232
1257
|
* `secrets` auth mode `"full"` needs in the tenant vault, BOTH sync directions —
|
|
1233
1258
|
* the inbound `$users` webhook mirror (`provider->odla`) and the outbound
|
|
1234
|
-
* role/profile write via `clerk_secret_key` (`odla->provider`,
|
|
1235
|
-
* `clerk.ts`) — and the `provision` split. Data only; it performs
|
|
1259
|
+
* public role/private profile write via `clerk_secret_key` (`odla->provider`,
|
|
1260
|
+
* chapter's `clerk.ts`) — and the `provision` split. Data only; it performs
|
|
1261
|
+
* none of it.
|
|
1236
1262
|
*/
|
|
1237
1263
|
declare const clerkIntegration: IntegrationDescriptor;
|
|
1238
1264
|
|
|
@@ -1491,4 +1517,4 @@ type ApplicationBookingPatch = {
|
|
|
1491
1517
|
* already there (never backward). */
|
|
1492
1518
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
1493
1519
|
|
|
1494
|
-
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 ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, 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 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 ResolvedNetwork, 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 StripeEvent, type StripeResult, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, formatChapterCopy, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, validateScheduling, verifyStripeSignature, webhookMutationId };
|
|
1520
|
+
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 ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, 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 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 ResolvedNetwork, 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 StripeEvent, type StripeResult, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, formatChapterCopy, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, updateClerkUserMetadata, updateClerkUserMetadataByEmail, validateScheduling, verifyStripeSignature, webhookMutationId };
|
package/dist/index.d.ts
CHANGED
|
@@ -327,10 +327,9 @@ interface ChapterApplication {
|
|
|
327
327
|
* writing a row with no consent record. Default `true`. Set `false`
|
|
328
328
|
* deliberately only when the site renders no consent control. */
|
|
329
329
|
requireDisclaimerAck?: boolean;
|
|
330
|
-
/** Allowlist of fields
|
|
331
|
-
* `
|
|
332
|
-
*
|
|
333
|
-
* fields the browser may read. */
|
|
330
|
+
/** Allowlist of application fields mirrored into the Clerk account's
|
|
331
|
+
* backend-only `private_metadata.profile`. Default `[]`. Use this for small
|
|
332
|
+
* account/admin signals; the application row and CRM remain canonical. */
|
|
334
333
|
profileFields?: readonly string[];
|
|
335
334
|
/** Extra application fields carried into the one-way CRM projection, on top of
|
|
336
335
|
* the built-in identity/contact set. Each MUST be declared on your crm person
|
|
@@ -877,11 +876,10 @@ declare function clampArray(value: unknown, max: number): unknown;
|
|
|
877
876
|
* the boolean an API client sends and the string a plain HTML form posts. */
|
|
878
877
|
declare function hasDisclaimerAck(fields: Record<string, unknown>): boolean;
|
|
879
878
|
/**
|
|
880
|
-
* The applicant profile written to the Clerk account's
|
|
881
|
-
* `
|
|
882
|
-
* `focus` (clamped), but ONLY those in `application.profileFields
|
|
883
|
-
*
|
|
884
|
-
* config, so a site's own field names project without this package knowing
|
|
879
|
+
* The applicant profile written to the Clerk account's backend-only
|
|
880
|
+
* `private_metadata.profile`. Projects each configured non-identity field, plus
|
|
881
|
+
* `focus` (clamped), but ONLY those in `application.profileFields`. Derived
|
|
882
|
+
* from config, so a site's own field names project without this package knowing
|
|
885
883
|
* them. Pure; returns `undefined` when there is nothing to write.
|
|
886
884
|
*/
|
|
887
885
|
declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
@@ -1097,7 +1095,7 @@ interface ClerkResult {
|
|
|
1097
1095
|
ok: boolean;
|
|
1098
1096
|
status: number;
|
|
1099
1097
|
existed?: boolean;
|
|
1100
|
-
/** Set when an `existed` heal also refreshed the account's
|
|
1098
|
+
/** Set when an `existed` heal also refreshed the account's metadata. */
|
|
1101
1099
|
refreshed?: boolean;
|
|
1102
1100
|
}
|
|
1103
1101
|
/** Inputs for a Clerk invitation. */
|
|
@@ -1105,8 +1103,9 @@ interface ClerkInviteInput {
|
|
|
1105
1103
|
email: string;
|
|
1106
1104
|
/** Where the accept-invitation link lands (usually the member area). */
|
|
1107
1105
|
redirectUrl?: string;
|
|
1108
|
-
/** Written to the invitation's `public_metadata
|
|
1109
|
-
*
|
|
1106
|
+
/** Written to the invitation's `public_metadata`. Clerk application
|
|
1107
|
+
* invitations cannot carry private metadata, so never put applicant profile
|
|
1108
|
+
* fields here. Chapter completes that backend-only write after acceptance. */
|
|
1110
1109
|
publicMetadata?: Record<string, unknown>;
|
|
1111
1110
|
}
|
|
1112
1111
|
/** Build the Clerk Backend API invitation request (path + JSON body). Pure, so
|
|
@@ -1124,9 +1123,14 @@ interface ClerkUserInput {
|
|
|
1124
1123
|
email: string;
|
|
1125
1124
|
firstName?: string;
|
|
1126
1125
|
lastName?: string;
|
|
1127
|
-
/**
|
|
1128
|
-
* (role, tier, whatever the member area reads). */
|
|
1126
|
+
/** Backend-authored, browser-readable claims such as `role`. */
|
|
1129
1127
|
publicMetadata?: Record<string, unknown>;
|
|
1128
|
+
/** Backend-only account signals and profile data. */
|
|
1129
|
+
privateMetadata?: Record<string, unknown>;
|
|
1130
|
+
/** Optional metadata merge used only when create returns 422 for an existing
|
|
1131
|
+
* account. This lets migrations remove legacy keys without putting tombstone
|
|
1132
|
+
* values on a newly-created user. Defaults to the create metadata. */
|
|
1133
|
+
repairMetadata?: ClerkMetadataInput;
|
|
1130
1134
|
}
|
|
1131
1135
|
/** Build the Clerk Backend API user-create request. The account is created
|
|
1132
1136
|
* passwordless (the member signs in via the site's Clerk flow), so join step 3
|
|
@@ -1135,21 +1139,41 @@ declare function clerkUserRequest(input: ClerkUserInput): {
|
|
|
1135
1139
|
path: string;
|
|
1136
1140
|
body: Record<string, unknown>;
|
|
1137
1141
|
};
|
|
1142
|
+
/** One merge-PATCH to the metadata fields Clerk exposes on its Backend API. */
|
|
1143
|
+
interface ClerkMetadataInput {
|
|
1144
|
+
publicMetadata?: Record<string, unknown>;
|
|
1145
|
+
privateMetadata?: Record<string, unknown>;
|
|
1146
|
+
}
|
|
1147
|
+
/** Merge metadata into an existing Clerk user. Nested `null` values remove old
|
|
1148
|
+
* keys, which Chapter uses to migrate its former public profile into private
|
|
1149
|
+
* metadata without disturbing the public `role` claim. */
|
|
1150
|
+
declare function updateClerkUserMetadata(secretKey: string, userId: string, input: ClerkMetadataInput, fetchImpl?: typeof fetch): Promise<boolean>;
|
|
1151
|
+
/** Look a Clerk account up by email and merge its metadata.
|
|
1152
|
+
*
|
|
1153
|
+
* Returns `false` when no matching user can be resolved or Clerk rejects the
|
|
1154
|
+
* lookup or update. Chapter uses this for create-healing and invitation
|
|
1155
|
+
* adoption so a repeated application repairs the private account snapshot.
|
|
1156
|
+
*/
|
|
1157
|
+
declare function updateClerkUserMetadataByEmail(secretKey: string, email: string, input: ClerkMetadataInput, fetchImpl?: typeof fetch): Promise<boolean>;
|
|
1138
1158
|
/** Create the applicant's Clerk account server-side. A repeat for an email that
|
|
1139
1159
|
* already has an account heals to `{ ok: true, existed: true }` — the account
|
|
1140
|
-
* exists, which is the state apply-time provisioning wanted — and
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1160
|
+
* exists, which is the state apply-time provisioning wanted — and refreshes
|
|
1161
|
+
* supplied public/private metadata on the existing account so a re-application
|
|
1162
|
+
* repairs a previously missed create (`refreshed: true`). */
|
|
1143
1163
|
declare function createClerkUser(secretKey: string, input: ClerkUserInput, fetchImpl?: typeof fetch): Promise<ClerkResult>;
|
|
1144
1164
|
|
|
1145
1165
|
/** A Clerk user as chapter's role layer sees it. `role` defaults to the lowest
|
|
1146
|
-
* rung when `public_metadata` carries none
|
|
1147
|
-
*
|
|
1166
|
+
* rung when `public_metadata` carries none. Raw public/private metadata is
|
|
1167
|
+
* returned only from this backend helper; `profile` and `applicationId` are the
|
|
1168
|
+
* normalized Chapter-owned private signals. */
|
|
1148
1169
|
interface ClerkUserRecord {
|
|
1149
1170
|
id: string;
|
|
1150
1171
|
email?: string;
|
|
1151
1172
|
role: string;
|
|
1152
1173
|
publicMetadata: Record<string, unknown>;
|
|
1174
|
+
privateMetadata: Record<string, unknown>;
|
|
1175
|
+
profile: Record<string, unknown>;
|
|
1176
|
+
applicationId?: string;
|
|
1153
1177
|
}
|
|
1154
1178
|
/** Look a Clerk user up by email. `null` when no such user — or when the lookup
|
|
1155
1179
|
* fails (a role gate treats an unresolvable user as absent, matching the site's
|
|
@@ -1164,8 +1188,9 @@ declare function clerkGetUser(secretKey: string, id: string, fetchImpl?: typeof
|
|
|
1164
1188
|
* until a short page. A page fetch that fails THROWS rather than returning a
|
|
1165
1189
|
* partial list, so the caller never mistakes a truncated roster for the whole. */
|
|
1166
1190
|
declare function clerkListUsers(secretKey: string, fetchImpl?: typeof fetch): Promise<ClerkUserRecord[]>;
|
|
1167
|
-
/** Write a user's role: a MERGE-PATCH of only `{ role }` on `public_metadata
|
|
1168
|
-
*
|
|
1191
|
+
/** Write a user's role: a MERGE-PATCH of only `{ role }` on `public_metadata`.
|
|
1192
|
+
* The backend-only profile lives in `private_metadata`, so the two signals
|
|
1193
|
+
* cannot overwrite each other. Returns whether the role write stuck. */
|
|
1169
1194
|
declare function clerkSetRole(secretKey: string, id: string, role: string, fetchImpl?: typeof fetch): Promise<boolean>;
|
|
1170
1195
|
|
|
1171
1196
|
/** Which auth mode a secret is required for: `"client"` (publishable key only) or
|
|
@@ -1231,8 +1256,9 @@ interface IntegrationDescriptor {
|
|
|
1231
1256
|
* the one public `setting` (the `pk_*` publishable key, served to the SPA), the
|
|
1232
1257
|
* `secrets` auth mode `"full"` needs in the tenant vault, BOTH sync directions —
|
|
1233
1258
|
* the inbound `$users` webhook mirror (`provider->odla`) and the outbound
|
|
1234
|
-
* role/profile write via `clerk_secret_key` (`odla->provider`,
|
|
1235
|
-
* `clerk.ts`) — and the `provision` split. Data only; it performs
|
|
1259
|
+
* public role/private profile write via `clerk_secret_key` (`odla->provider`,
|
|
1260
|
+
* chapter's `clerk.ts`) — and the `provision` split. Data only; it performs
|
|
1261
|
+
* none of it.
|
|
1236
1262
|
*/
|
|
1237
1263
|
declare const clerkIntegration: IntegrationDescriptor;
|
|
1238
1264
|
|
|
@@ -1491,4 +1517,4 @@ type ApplicationBookingPatch = {
|
|
|
1491
1517
|
* already there (never backward). */
|
|
1492
1518
|
declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
|
|
1493
1519
|
|
|
1494
|
-
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 ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, 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 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 ResolvedNetwork, 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 StripeEvent, type StripeResult, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, formatChapterCopy, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, validateScheduling, verifyStripeSignature, webhookMutationId };
|
|
1520
|
+
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 ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, 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 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 ResolvedNetwork, 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 StripeEvent, type StripeResult, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, formatChapterCopy, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, updateClerkUserMetadata, updateClerkUserMetadataByEmail, validateScheduling, verifyStripeSignature, webhookMutationId };
|
package/dist/index.js
CHANGED
|
@@ -44,6 +44,10 @@ var applications = {
|
|
|
44
44
|
meetingAt: attr("number", { indexed: true, optional: true }),
|
|
45
45
|
meetingLink: attr("string", { optional: true }),
|
|
46
46
|
clerkUserId: attr("string", { indexed: true, optional: true }),
|
|
47
|
+
// One-time/effect-repair marker. Absent means /api/me should copy the
|
|
48
|
+
// allowlisted application snapshot into Clerk private_metadata and remove
|
|
49
|
+
// the former public_metadata copy.
|
|
50
|
+
clerkPrivateMetadataSyncedAt: attr("number", { optional: true }),
|
|
47
51
|
phone: attr("string", { optional: true }),
|
|
48
52
|
state: attr("string", { optional: true }),
|
|
49
53
|
groupId: attr("string", { indexed: true, optional: true }),
|
|
@@ -1502,23 +1506,30 @@ function clerkUserRequest(input) {
|
|
|
1502
1506
|
skip_password_requirement: true,
|
|
1503
1507
|
...input.firstName ? { first_name: input.firstName } : {},
|
|
1504
1508
|
...input.lastName ? { last_name: input.lastName } : {},
|
|
1505
|
-
...input.publicMetadata ? { public_metadata: input.publicMetadata } : {}
|
|
1509
|
+
...input.publicMetadata !== void 0 ? { public_metadata: input.publicMetadata } : {},
|
|
1510
|
+
...input.privateMetadata !== void 0 ? { private_metadata: input.privateMetadata } : {}
|
|
1506
1511
|
}
|
|
1507
1512
|
};
|
|
1508
1513
|
}
|
|
1509
|
-
async function
|
|
1514
|
+
async function updateClerkUserMetadata(secretKey, userId, input, fetchImpl = fetch) {
|
|
1515
|
+
const res = await fetchImpl(`https://api.clerk.com/v1/users/${encodeURIComponent(userId)}/metadata`, {
|
|
1516
|
+
method: "PATCH",
|
|
1517
|
+
headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
|
|
1518
|
+
body: JSON.stringify({
|
|
1519
|
+
...input.publicMetadata !== void 0 ? { public_metadata: input.publicMetadata } : {},
|
|
1520
|
+
...input.privateMetadata !== void 0 ? { private_metadata: input.privateMetadata } : {}
|
|
1521
|
+
})
|
|
1522
|
+
});
|
|
1523
|
+
return res.ok;
|
|
1524
|
+
}
|
|
1525
|
+
async function updateClerkUserMetadataByEmail(secretKey, email, input, fetchImpl = fetch) {
|
|
1510
1526
|
const auth = { authorization: `Bearer ${secretKey}` };
|
|
1511
1527
|
const found = await fetchImpl(`https://api.clerk.com/v1/users?email_address=${encodeURIComponent(email)}&limit=1`, { headers: auth });
|
|
1512
1528
|
if (!found.ok) return false;
|
|
1513
1529
|
const users = await found.json().catch(() => null);
|
|
1514
1530
|
const id2 = Array.isArray(users) && typeof users[0]?.id === "string" ? users[0].id : void 0;
|
|
1515
1531
|
if (!id2) return false;
|
|
1516
|
-
|
|
1517
|
-
method: "PATCH",
|
|
1518
|
-
headers: { ...auth, "content-type": "application/json" },
|
|
1519
|
-
body: JSON.stringify({ public_metadata: publicMetadata })
|
|
1520
|
-
});
|
|
1521
|
-
return patched.ok;
|
|
1532
|
+
return updateClerkUserMetadata(secretKey, id2, input, fetchImpl);
|
|
1522
1533
|
}
|
|
1523
1534
|
async function createClerkUser(secretKey, input, fetchImpl = fetch) {
|
|
1524
1535
|
const { path, body } = clerkUserRequest(input);
|
|
@@ -1529,8 +1540,15 @@ async function createClerkUser(secretKey, input, fetchImpl = fetch) {
|
|
|
1529
1540
|
});
|
|
1530
1541
|
if (res.ok) return { ok: true, status: res.status };
|
|
1531
1542
|
const healed = heal(res.status);
|
|
1532
|
-
|
|
1533
|
-
|
|
1543
|
+
const repair = input.repairMetadata ?? {
|
|
1544
|
+
...input.publicMetadata !== void 0 ? { publicMetadata: input.publicMetadata } : {},
|
|
1545
|
+
...input.privateMetadata !== void 0 ? { privateMetadata: input.privateMetadata } : {}
|
|
1546
|
+
};
|
|
1547
|
+
if (!healed.existed || repair.publicMetadata === void 0 && repair.privateMetadata === void 0) return healed;
|
|
1548
|
+
const refreshed = await updateClerkUserMetadataByEmail(secretKey, input.email, {
|
|
1549
|
+
...repair.publicMetadata !== void 0 ? { publicMetadata: repair.publicMetadata } : {},
|
|
1550
|
+
...repair.privateMetadata !== void 0 ? { privateMetadata: repair.privateMetadata } : {}
|
|
1551
|
+
}, fetchImpl).catch(() => false);
|
|
1534
1552
|
return { ...healed, refreshed };
|
|
1535
1553
|
}
|
|
1536
1554
|
|
|
@@ -1541,9 +1559,21 @@ var PAGE = 100;
|
|
|
1541
1559
|
function toRecord(u) {
|
|
1542
1560
|
if (typeof u.id !== "string") return null;
|
|
1543
1561
|
const pm = u.public_metadata ?? {};
|
|
1562
|
+
const privateMetadata = u.private_metadata ?? {};
|
|
1563
|
+
const rawProfile = privateMetadata.profile;
|
|
1564
|
+
const profile = rawProfile && typeof rawProfile === "object" && !Array.isArray(rawProfile) ? rawProfile : {};
|
|
1565
|
+
const applicationId = typeof privateMetadata.applicationId === "string" ? privateMetadata.applicationId : void 0;
|
|
1544
1566
|
const role = typeof pm.role === "string" && pm.role ? pm.role : DEFAULT_ROLE;
|
|
1545
1567
|
const email = u.email_addresses?.[0]?.email_address;
|
|
1546
|
-
return {
|
|
1568
|
+
return {
|
|
1569
|
+
id: u.id,
|
|
1570
|
+
email: typeof email === "string" ? email : void 0,
|
|
1571
|
+
role,
|
|
1572
|
+
publicMetadata: pm,
|
|
1573
|
+
privateMetadata,
|
|
1574
|
+
profile,
|
|
1575
|
+
...applicationId ? { applicationId } : {}
|
|
1576
|
+
};
|
|
1547
1577
|
}
|
|
1548
1578
|
async function clerkGet(path, secretKey, fetchImpl) {
|
|
1549
1579
|
const res = await fetchImpl(`${CLERK_API}${path}`, { headers: { authorization: `Bearer ${secretKey}` } });
|
|
@@ -1606,7 +1636,7 @@ var clerkIntegration = {
|
|
|
1606
1636
|
},
|
|
1607
1637
|
{
|
|
1608
1638
|
key: "clerk_secret_key",
|
|
1609
|
-
description: "Clerk backend key (sk_*) \u2014 powers the
|
|
1639
|
+
description: "Clerk backend key (sk_*) \u2014 powers account creation, the public role claim, and backend-only private profile signals; it also lets odla-db resolve user email/name via the Clerk API.",
|
|
1610
1640
|
pattern: "sk_",
|
|
1611
1641
|
mode: "full",
|
|
1612
1642
|
vault: true
|
|
@@ -1626,8 +1656,13 @@ var clerkIntegration = {
|
|
|
1626
1656
|
engine: "@odla-ai/chapter clerk.ts (Clerk Backend API via vault clerk_secret_key)",
|
|
1627
1657
|
direction: "odla->provider",
|
|
1628
1658
|
entity: "clerk user",
|
|
1629
|
-
// Separate
|
|
1630
|
-
|
|
1659
|
+
// Separate metadata fields so a public role write cannot expose or
|
|
1660
|
+
// clobber backend-only account/profile signals.
|
|
1661
|
+
fields: [
|
|
1662
|
+
"public_metadata.role",
|
|
1663
|
+
"private_metadata.applicationId",
|
|
1664
|
+
"private_metadata.profile"
|
|
1665
|
+
],
|
|
1631
1666
|
onDelete: "n/a (writes only)"
|
|
1632
1667
|
}
|
|
1633
1668
|
],
|
|
@@ -2004,6 +2039,8 @@ export {
|
|
|
2004
2039
|
submitApplication,
|
|
2005
2040
|
subscriptionIdempotencyKey,
|
|
2006
2041
|
syncApplicationToCrm,
|
|
2042
|
+
updateClerkUserMetadata,
|
|
2043
|
+
updateClerkUserMetadataByEmail,
|
|
2007
2044
|
validateScheduling,
|
|
2008
2045
|
verifyStripeSignature,
|
|
2009
2046
|
webhookMutationId
|