@odla-ai/chapter 0.56.2 → 0.58.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 +68 -0
- package/dist/{chunk-E7CB5K2D.js → chunk-CKC6X5H2.js} +47 -3
- package/dist/chunk-CKC6X5H2.js.map +1 -0
- package/dist/{chunk-4M2PN5K3.js → chunk-FHKZVF5G.js} +3 -1
- package/dist/chunk-FHKZVF5G.js.map +1 -0
- package/dist/{chunk-ZAE24YKY.js → chunk-L2IPS2FQ.js} +51 -4
- package/dist/chunk-L2IPS2FQ.js.map +1 -0
- package/dist/{copy-context-CaUXIxUI.d.ts → copy-context-DE4YMAbz.d.ts} +1 -1
- package/dist/index.cjs +178 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -5
- package/dist/index.d.ts +38 -5
- package/dist/index.js +178 -15
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +4 -3
- package/dist/ui/admin/index.js +2 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/dist/ui/member/index.d.ts +10 -3
- package/dist/ui/member/index.js +2 -2
- package/dist/worker/index.cjs +1580 -1129
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +149 -3
- package/dist/worker/index.d.ts +149 -3
- package/dist/worker/index.js +1580 -1129
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/runbooks/greenfield.md +1 -1
- package/dist/chunk-4M2PN5K3.js.map +0 -1
- package/dist/chunk-E7CB5K2D.js.map +0 -1
- package/dist/chunk-ZAE24YKY.js.map +0 -1
|
@@ -45,7 +45,7 @@ type AdminRecordsCopy = {
|
|
|
45
45
|
[Field in AdminRecordsCopyKey]: string;
|
|
46
46
|
};
|
|
47
47
|
/** Every key a host may override under `admin.records`. */
|
|
48
|
-
type AdminRecordsCopyKey = "workflowMissing" | "invitation" | "comms" | "commsHistory" | "scheduling" | "billing" | "notes" | "access" | "sharing" | "lifecycle" | "role" | "superAdminHint" | "noAccount" | "approve" | "refund" | "approved" | "refunded" | "roleSet" | "messageSent" | "messageNotSent" | "loadingTemplates" | "template" | "chooseTemplate" | "sending" | "send" | "noMessages" | "message" | "newStartPrompt" | "parseFailed" | "cancelConfirm" | "callCancelled" | "callRescheduled" | "noApplication" | "loadingMeetings" | "noMeetings" | "joinMeeting" | "openCalendar" | "reschedule" | "cancel";
|
|
48
|
+
type AdminRecordsCopyKey = "workflowMissing" | "invitation" | "comms" | "commsHistory" | "scheduling" | "billing" | "notes" | "access" | "sharing" | "lifecycle" | "role" | "superAdminHint" | "noAccount" | "approve" | "decline" | "refund" | "approved" | "declined" | "refunded" | "roleSet" | "messageSent" | "messageNotSent" | "loadingTemplates" | "template" | "chooseTemplate" | "sending" | "send" | "noMessages" | "message" | "newStartPrompt" | "parseFailed" | "cancelConfirm" | "callCancelled" | "callRescheduled" | "noApplication" | "loadingMeetings" | "noMeetings" | "joinMeeting" | "openCalendar" | "reschedule" | "cancel";
|
|
49
49
|
|
|
50
50
|
type TextFields<Key extends string> = {
|
|
51
51
|
[Field in Key]: string;
|
package/dist/index.cjs
CHANGED
|
@@ -230,6 +230,7 @@ __export(src_exports, {
|
|
|
230
230
|
requestNamedSeatOperatorRefund: () => requestNamedSeatOperatorRefund,
|
|
231
231
|
requestNamedSeatPayment: () => requestNamedSeatPayment,
|
|
232
232
|
requestNamedSeatRefund: () => requestNamedSeatRefund2,
|
|
233
|
+
requestNamedSeatRegift: () => requestNamedSeatRegift,
|
|
233
234
|
requestNamedSeatReservation: () => requestNamedSeatReservation,
|
|
234
235
|
resolveApplication: () => resolveApplication,
|
|
235
236
|
resolveAuth: () => resolveAuth,
|
|
@@ -421,6 +422,7 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
421
422
|
clerkUserId: opts.namedSeat.userId,
|
|
422
423
|
clerkAccountPending: false
|
|
423
424
|
});
|
|
425
|
+
if (opts.predecessor?.clerkUserId && !opts.namedSeat) Object.assign(row2, { clerkUserId: opts.predecessor.clerkUserId, clerkAccountPending: false });
|
|
424
426
|
if (typeof fields.email === "string" && isValidEmail(fields.email.trim())) {
|
|
425
427
|
row2.emailNormalized = normalizeChapterEmail(fields.email);
|
|
426
428
|
row2.signupEmailKey = row2.emailNormalized;
|
|
@@ -439,6 +441,8 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
439
441
|
}
|
|
440
442
|
if (acked) row2.disclaimerAckAt = opts.now;
|
|
441
443
|
const ops = [
|
|
444
|
+
// The unique signup key leaves the closed predecessor before the new attempt takes it.
|
|
445
|
+
...opts.predecessor && opts.predecessor.id !== id11 ? [{ t: "retract", ns: "applications", id: opts.predecessor.id, attrs: ["signupEmailKey"] }] : [],
|
|
442
446
|
{ t: "update", ns: "applications", id: id11, attrs: row2 }
|
|
443
447
|
];
|
|
444
448
|
if (opts.admissionGrant?.decision === "waive_interview") {
|
|
@@ -1375,7 +1379,7 @@ var DEFAULT_SCHEDULING = {
|
|
|
1375
1379
|
endHour: 17,
|
|
1376
1380
|
timezone: "America/Los_Angeles",
|
|
1377
1381
|
minNoticeHours: 24,
|
|
1378
|
-
windowDays:
|
|
1382
|
+
windowDays: 21
|
|
1379
1383
|
};
|
|
1380
1384
|
function defaultEmailTemplates(name) {
|
|
1381
1385
|
const sign = `
|
|
@@ -1774,8 +1778,10 @@ var DEFAULT_ADMIN_COPY = {
|
|
|
1774
1778
|
superAdminHint: "Super-admin, managed in odla Studio.",
|
|
1775
1779
|
noAccount: "No linked account yet. The role is set once they sign in.",
|
|
1776
1780
|
approve: "Approve",
|
|
1781
|
+
decline: "Decline",
|
|
1777
1782
|
refund: "Refund",
|
|
1778
1783
|
approved: "Approved.",
|
|
1784
|
+
declined: "Declined.",
|
|
1779
1785
|
refunded: "Refund issued.",
|
|
1780
1786
|
roleSet: "Role set to {role}.",
|
|
1781
1787
|
messageSent: "Message sent.",
|
|
@@ -4555,7 +4561,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
|
|
|
4555
4561
|
var import_stripe4 = require("@odla-ai/stripe");
|
|
4556
4562
|
|
|
4557
4563
|
// src/membership-seat-reservation.ts
|
|
4558
|
-
async function
|
|
4564
|
+
async function namedSeatRecipientClaimId(input, email) {
|
|
4559
4565
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([input.appId, input.chapterId, input.environment, input.runtime, email])));
|
|
4560
4566
|
return `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
4561
4567
|
}
|
|
@@ -4572,7 +4578,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
4572
4578
|
const id11 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
4573
4579
|
let seat = await membershipLoad(db, "networkSeat", id11);
|
|
4574
4580
|
if (!seat) {
|
|
4575
|
-
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await
|
|
4581
|
+
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await namedSeatRecipientClaimId(input, email);
|
|
4576
4582
|
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
4577
4583
|
if (!["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || now < Number(member.initialTermStartedAt) || now >= Number(member.termEndsAt) || now > purchaseEndsAt || !Number.isSafeInteger(purchaseEndsAt) || member.namedSeatId !== void 0) {
|
|
4578
4584
|
membershipFail("named_seat_purchase_unavailable");
|
|
@@ -5661,6 +5667,128 @@ async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
|
|
|
5661
5667
|
return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
|
|
5662
5668
|
}
|
|
5663
5669
|
|
|
5670
|
+
// src/membership-effect-kinds.ts
|
|
5671
|
+
var MEMBERSHIP_CHAPTER_EFFECT_KINDS = [
|
|
5672
|
+
"invite_named_recipient",
|
|
5673
|
+
"grant_named_recipient_access",
|
|
5674
|
+
"revoke_named_recipient_access",
|
|
5675
|
+
"release_named_recipient"
|
|
5676
|
+
];
|
|
5677
|
+
var namedSeatInvitationOpen = (member) => ["active", "cancel_at_term_end"].includes(String(member.status)) && Number.isSafeInteger(member.journeyApprovedAt);
|
|
5678
|
+
|
|
5679
|
+
// src/membership-seat-regift.ts
|
|
5680
|
+
var REGIFTABLE_SEAT_STATUSES = ["pending_acceptance", "pending_approval", "pending_primary"];
|
|
5681
|
+
var namedSeatRegiftable = (seat) => REGIFTABLE_SEAT_STATUSES.includes(String(seat.status));
|
|
5682
|
+
var receiptFor = (seat, released) => ({
|
|
5683
|
+
seatId: String(seat.id),
|
|
5684
|
+
status: "pending_acceptance",
|
|
5685
|
+
recipientName: String(seat.recipientName),
|
|
5686
|
+
recipientEmail: String(seat.recipientEmailNormalized),
|
|
5687
|
+
regiftedAt: Number(seat.regiftedAt),
|
|
5688
|
+
releasedRecipientApplicationId: released
|
|
5689
|
+
});
|
|
5690
|
+
async function regiftNamedSeat(db, input, now) {
|
|
5691
|
+
membershipClock(now);
|
|
5692
|
+
const name = typeof input.recipientName === "string" ? input.recipientName.trim().replace(/\s+/g, " ") : "";
|
|
5693
|
+
const email = normalizeChapterEmail(typeof input.recipientEmail === "string" ? input.recipientEmail : "");
|
|
5694
|
+
if (name.length < 2 || name.length > 160 || /[\u0000-\u001f\u007f]/.test(input.recipientName) || email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) membershipFail("invalid_named_seat_recipient");
|
|
5695
|
+
const state = await loadMembershipAuthorityState(db, input);
|
|
5696
|
+
const seat = await membershipLoad(db, "networkSeat", input.seatId);
|
|
5697
|
+
requireMembershipScope(seat, input);
|
|
5698
|
+
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
5699
|
+
requireMembershipScope(member, input);
|
|
5700
|
+
if (member.namedSeatId !== seat.id) membershipFail("named_seat_primary_conflict");
|
|
5701
|
+
if (typeof seat.purchaserEmailNormalized === "string" && seat.purchaserEmailNormalized === email) membershipFail("invalid_named_seat_recipient");
|
|
5702
|
+
const history = Array.isArray(seat.formerRecipients) ? seat.formerRecipients : [];
|
|
5703
|
+
if (seat.recipientEmailNormalized === email && seat.recipientName === name && seat.regiftedAt !== void 0 && seat.status === "pending_acceptance") {
|
|
5704
|
+
return receiptFor(seat, history.at(-1)?.recipientApplicationId ?? null);
|
|
5705
|
+
}
|
|
5706
|
+
if (!namedSeatRegiftable(seat)) membershipFail("named_seat_not_regiftable");
|
|
5707
|
+
if (seat.recipientEmailNormalized === email) membershipFail("named_seat_recipient_unchanged");
|
|
5708
|
+
const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
|
|
5709
|
+
requireMembershipScope(entitlement, input);
|
|
5710
|
+
const claimId = await namedSeatRecipientClaimId(input, email);
|
|
5711
|
+
const former = {
|
|
5712
|
+
recipientName: String(seat.recipientName),
|
|
5713
|
+
recipientEmail: String(seat.recipientEmailNormalized),
|
|
5714
|
+
recipientClaimId: String(seat.recipientClaimId),
|
|
5715
|
+
releasedAt: now,
|
|
5716
|
+
...typeof seat.recipientUserId === "string" ? { recipientUserId: seat.recipientUserId } : {},
|
|
5717
|
+
...typeof seat.recipientApplicationId === "string" ? { recipientApplicationId: seat.recipientApplicationId } : {},
|
|
5718
|
+
...Number.isSafeInteger(seat.acceptedAt) ? { acceptedAt: Number(seat.acceptedAt) } : {},
|
|
5719
|
+
...Number.isSafeInteger(seat.journeyApprovedAt) ? { journeyApprovedAt: Number(seat.journeyApprovedAt) } : {}
|
|
5720
|
+
};
|
|
5721
|
+
const generation = history.length + 1, inviteId = `${seat.id}:invite:${generation}`, releaseId = `${seat.id}:release:${generation}`;
|
|
5722
|
+
const bound = Boolean(former.recipientUserId && former.recipientApplicationId);
|
|
5723
|
+
const stale = ((await db.query({ membershipEffect: { $: { where: { seatId: seat.id, kind: "invite_named_recipient", status: "pending" }, limit: 10 } } })).membershipEffect ?? []).filter((row2) => row2.chapterId === input.chapterId && row2.environment === input.environment && row2.runtime === input.runtime);
|
|
5724
|
+
const cleared = ["recipientUserId", "recipientApplicationId", "acceptedAt", "journeyApprovedAt"];
|
|
5725
|
+
const effect = (id11, kind, extra = {}) => membershipUpdate("membershipEffect", id11, {
|
|
5726
|
+
id: id11,
|
|
5727
|
+
chapterId: input.chapterId,
|
|
5728
|
+
environment: input.environment,
|
|
5729
|
+
runtime: input.runtime,
|
|
5730
|
+
membershipId: member.id,
|
|
5731
|
+
seatId: seat.id,
|
|
5732
|
+
kind,
|
|
5733
|
+
status: "pending",
|
|
5734
|
+
attempts: 0,
|
|
5735
|
+
createdAt: now,
|
|
5736
|
+
updatedAt: now,
|
|
5737
|
+
...extra
|
|
5738
|
+
});
|
|
5739
|
+
const ops = [
|
|
5740
|
+
membershipUpdate("networkSeat", String(seat.id), {
|
|
5741
|
+
recipientName: name,
|
|
5742
|
+
recipientEmailNormalized: email,
|
|
5743
|
+
recipientClaimId: claimId,
|
|
5744
|
+
status: "pending_acceptance",
|
|
5745
|
+
regiftedAt: now,
|
|
5746
|
+
formerRecipients: [...history, former],
|
|
5747
|
+
revision: Number(seat.revision) + 1,
|
|
5748
|
+
updatedAt: now
|
|
5749
|
+
}),
|
|
5750
|
+
{ t: "retract", ns: "networkSeat", id: String(seat.id), attrs: cleared },
|
|
5751
|
+
membershipUpdate("networkEntitlement", String(entitlement.id), { status: "pending_acceptance", revision: Number(entitlement.revision) + 1, updatedAt: now }),
|
|
5752
|
+
{ t: "retract", ns: "networkEntitlement", id: String(entitlement.id), attrs: cleared },
|
|
5753
|
+
{ t: "delete", ns: "networkSeatRecipient", id: String(seat.recipientClaimId) },
|
|
5754
|
+
membershipUpdate("networkSeatRecipient", claimId, {
|
|
5755
|
+
id: claimId,
|
|
5756
|
+
appId: input.appId,
|
|
5757
|
+
chapterId: input.chapterId,
|
|
5758
|
+
environment: input.environment,
|
|
5759
|
+
runtime: input.runtime,
|
|
5760
|
+
seatId: seat.id,
|
|
5761
|
+
recipientEmailNormalized: email,
|
|
5762
|
+
createdAt: now
|
|
5763
|
+
}),
|
|
5764
|
+
...stale.map((row2) => membershipUpdate("membershipEffect", String(row2.id), { status: "complete", updatedAt: now })),
|
|
5765
|
+
effect(inviteId, "invite_named_recipient"),
|
|
5766
|
+
...bound ? [effect(releaseId, "release_named_recipient", { recipientUserId: former.recipientUserId, recipientApplicationId: former.recipientApplicationId })] : []
|
|
5767
|
+
];
|
|
5768
|
+
try {
|
|
5769
|
+
await db.transact(ops, { mutationId: `named-seat-regift:${seat.id}:${generation}`, guards: [
|
|
5770
|
+
{ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision, status: seat.status } },
|
|
5771
|
+
{ ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } },
|
|
5772
|
+
{ ns: "networkMembership", id: String(member.id), exists: true, equals: { revision: member.revision, namedSeatId: seat.id } },
|
|
5773
|
+
{ ns: "networkSeatRecipient", id: claimId, exists: false },
|
|
5774
|
+
...stale.map((row2) => ({ ns: "membershipEffect", id: String(row2.id), exists: true, equals: { status: "pending" } })),
|
|
5775
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
5776
|
+
] });
|
|
5777
|
+
} catch (error) {
|
|
5778
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
5779
|
+
const current = await membershipLoad(db, "networkSeat", input.seatId);
|
|
5780
|
+
requireMembershipScope(current, input);
|
|
5781
|
+
if (current.recipientEmailNormalized === email && current.status === "pending_acceptance") return receiptFor(current, former.recipientApplicationId ?? null);
|
|
5782
|
+
const claim = await membershipLoad(db, "networkSeatRecipient", claimId);
|
|
5783
|
+
if (claim && claim.seatId !== seat.id) membershipFail("named_seat_recipient_unavailable");
|
|
5784
|
+
membershipFail("named_seat_regift_retry_required");
|
|
5785
|
+
}
|
|
5786
|
+
const saved = await membershipLoad(db, "networkSeat", input.seatId);
|
|
5787
|
+
requireMembershipScope(saved, input);
|
|
5788
|
+
if (saved.recipientEmailNormalized !== email || saved.status !== "pending_acceptance") membershipFail("named_seat_regift_retry_required");
|
|
5789
|
+
return receiptFor(saved, former.recipientApplicationId ?? null);
|
|
5790
|
+
}
|
|
5791
|
+
|
|
5664
5792
|
// src/membership-seat-offer.ts
|
|
5665
5793
|
async function namedSeatOfferDigest(offer) {
|
|
5666
5794
|
const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
|
|
@@ -5704,7 +5832,10 @@ async function readNamedSeatOffer(db, input, now) {
|
|
|
5704
5832
|
recipientState: namedSeatRecipientState(seat),
|
|
5705
5833
|
...at(seat.purchasedAt) !== void 0 ? { purchasedAt: at(seat.purchasedAt) } : {},
|
|
5706
5834
|
...at(seat.acceptedAt) !== void 0 ? { acceptedAt: at(seat.acceptedAt) } : {},
|
|
5707
|
-
...seat.status === "active" && at(seat.journeyApprovedAt) !== void 0 ? { joinedAt: at(seat.journeyApprovedAt) } : {}
|
|
5835
|
+
...seat.status === "active" && at(seat.journeyApprovedAt) !== void 0 ? { joinedAt: at(seat.journeyApprovedAt) } : {},
|
|
5836
|
+
...seat.status === "pending_acceptance" && seat.acceptedAt === void 0 && !namedSeatInvitationOpen(member) ? { invitationHeld: true } : {},
|
|
5837
|
+
...namedSeatRegiftable(seat) ? { regiftable: true } : {},
|
|
5838
|
+
...at(seat.regiftedAt) !== void 0 ? { regiftedAt: at(seat.regiftedAt) } : {}
|
|
5708
5839
|
} } : {}
|
|
5709
5840
|
};
|
|
5710
5841
|
return {
|
|
@@ -5752,10 +5883,14 @@ async function readMembershipChapterEffect(db, input, now) {
|
|
|
5752
5883
|
requireMembershipScope(seat, input);
|
|
5753
5884
|
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
5754
5885
|
requireMembershipScope(member, input);
|
|
5755
|
-
if (!effect || effect.kind !== input.kind || effect.seatId !== seat.id || effect.membershipId !== member.id || ["chapterId", "environment", "runtime"].some((key3) => effect[key3] !== seat[key3]) || !
|
|
5886
|
+
if (!effect || effect.kind !== input.kind || effect.seatId !== seat.id || effect.membershipId !== member.id || ["chapterId", "environment", "runtime"].some((key3) => effect[key3] !== seat[key3]) || !MEMBERSHIP_CHAPTER_EFFECT_KINDS.includes(input.kind)) throw new Error("invalid chapter effect");
|
|
5887
|
+
const released = input.kind === "release_named_recipient";
|
|
5888
|
+
const recipientUserId = released ? effect.recipientUserId : seat.recipientUserId;
|
|
5889
|
+
const recipientApplicationId = released ? effect.recipientApplicationId : seat.recipientApplicationId;
|
|
5756
5890
|
let applicable;
|
|
5757
|
-
if (input.kind === "invite_named_recipient") applicable = seat.status === "pending_acceptance" && seat.acceptedAt === void 0 && now < Number(member.termEndsAt) &&
|
|
5891
|
+
if (input.kind === "invite_named_recipient") applicable = seat.status === "pending_acceptance" && seat.acceptedAt === void 0 && now < Number(member.termEndsAt) && namedSeatInvitationOpen(member);
|
|
5758
5892
|
else if (input.kind === "revoke_named_recipient_access") applicable = ["refund_pending", "refunded"].includes(String(seat.status));
|
|
5893
|
+
else if (released) applicable = typeof recipientApplicationId === "string" && seat.recipientApplicationId !== recipientApplicationId;
|
|
5759
5894
|
else applicable = (await readNamedSeatAccess(db, {
|
|
5760
5895
|
...input,
|
|
5761
5896
|
recipientUserId: String(seat.recipientUserId),
|
|
@@ -5765,8 +5900,8 @@ async function readMembershipChapterEffect(db, input, now) {
|
|
|
5765
5900
|
...input,
|
|
5766
5901
|
recipientName: String(seat.recipientName),
|
|
5767
5902
|
recipientEmail: String(seat.recipientEmailNormalized),
|
|
5768
|
-
recipientUserId: typeof
|
|
5769
|
-
recipientApplicationId: typeof
|
|
5903
|
+
recipientUserId: typeof recipientUserId === "string" ? recipientUserId : null,
|
|
5904
|
+
recipientApplicationId: typeof recipientApplicationId === "string" ? recipientApplicationId : null,
|
|
5770
5905
|
acceptanceEndsAt: Number(seat.acceptanceEndsAt),
|
|
5771
5906
|
termEndsAt: Number(seat.termEndsAt),
|
|
5772
5907
|
applicable
|
|
@@ -5787,7 +5922,8 @@ var MEMBERSHIP_SEAT_ACTION_FIELDS = {
|
|
|
5787
5922
|
"seat-accept": recipient,
|
|
5788
5923
|
"seat-approve": recipient,
|
|
5789
5924
|
"seat-decline": recipient,
|
|
5790
|
-
"seat-effect": ["seatId", "effectId", "kind"]
|
|
5925
|
+
"seat-effect": ["seatId", "effectId", "kind"],
|
|
5926
|
+
"seat-regift": ["seatId", "recipientName", "recipientEmail"]
|
|
5791
5927
|
};
|
|
5792
5928
|
async function handleMembershipSeatAction(action, db, peer, input, now, stripeSecretName) {
|
|
5793
5929
|
if (!peer.seatPriceId) throw new Error("named seats are not configured");
|
|
@@ -5812,6 +5948,12 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
|
|
|
5812
5948
|
kind: String(input.kind)
|
|
5813
5949
|
}, now);
|
|
5814
5950
|
if (action === "seat-offer") return readNamedSeatOffer(db, target, now);
|
|
5951
|
+
if (action === "seat-regift") return regiftNamedSeat(db, {
|
|
5952
|
+
...target,
|
|
5953
|
+
seatId: String(input.seatId),
|
|
5954
|
+
recipientName: String(input.recipientName),
|
|
5955
|
+
recipientEmail: String(input.recipientEmail)
|
|
5956
|
+
}, now);
|
|
5815
5957
|
if (action === "seat-reservations") {
|
|
5816
5958
|
const seat = await reserveNamedMembershipSeat(db, {
|
|
5817
5959
|
...target,
|
|
@@ -6189,6 +6331,18 @@ async function requestNamedSeatOperatorRefund(options) {
|
|
|
6189
6331
|
if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents < 0) throw new Error("invalid named seat refund receipt");
|
|
6190
6332
|
return seat;
|
|
6191
6333
|
}
|
|
6334
|
+
async function requestNamedSeatRegift(options) {
|
|
6335
|
+
const result = await requestAuthority(options, "seat-regift", {
|
|
6336
|
+
applicationId: options.applicationId,
|
|
6337
|
+
skuId: options.skuId,
|
|
6338
|
+
seatId: options.seatId,
|
|
6339
|
+
recipientName: options.recipientName,
|
|
6340
|
+
recipientEmail: options.recipientEmail
|
|
6341
|
+
});
|
|
6342
|
+
const seat = result.seat;
|
|
6343
|
+
if (!seat || seat.seatId !== options.seatId || seat.status !== "pending_acceptance" || typeof seat.recipientName !== "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(seat.recipientEmail)) || !Number.isSafeInteger(seat.regiftedAt) || seat.releasedRecipientApplicationId !== null && typeof seat.releasedRecipientApplicationId !== "string") throw new Error("invalid named seat regift receipt");
|
|
6344
|
+
return seat;
|
|
6345
|
+
}
|
|
6192
6346
|
async function requestNamedSeatReservation(options) {
|
|
6193
6347
|
const result = await requestAuthority(options, "seat-reservations", {
|
|
6194
6348
|
applicationId: options.applicationId,
|
|
@@ -6434,7 +6588,7 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
6434
6588
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !options.stripeSecretKey) throw new TypeError("invalid membership recovery configuration");
|
|
6435
6589
|
await loadMembershipAuthorityState(db, scope2);
|
|
6436
6590
|
const matches2 = (row2) => Object.entries(scope2).every(([key3, value]) => row2[key3] === value);
|
|
6437
|
-
let pending = 0, complete = 0, more = false;
|
|
6591
|
+
let pending = 0, complete = 0, held = 0, more = false;
|
|
6438
6592
|
for (const status2 of ["payment_pending", "payment_processing", "payment_canceling"]) {
|
|
6439
6593
|
const seats = (await db.query({ networkSeat: { $: { where: { ...scope2, status: status2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkSeat ?? [];
|
|
6440
6594
|
more ||= seats.length > limit;
|
|
@@ -6470,6 +6624,10 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
6470
6624
|
requireMembershipScope(seat, target);
|
|
6471
6625
|
if (seat.primaryMembershipId !== member.id) throw new Error("membership seat scope mismatch");
|
|
6472
6626
|
}
|
|
6627
|
+
if (effect.kind === "invite_named_recipient" && seat?.acceptedAt === void 0 && !namedSeatInvitationOpen(member) && !["refund_pending", "refunded"].includes(String(seat?.status))) {
|
|
6628
|
+
held++;
|
|
6629
|
+
continue;
|
|
6630
|
+
}
|
|
6473
6631
|
try {
|
|
6474
6632
|
const attempts = Number(effect.attempts);
|
|
6475
6633
|
if (!Number.isSafeInteger(attempts) || attempts < 0) throw new Error("invalid membership effect attempt");
|
|
@@ -6493,9 +6651,10 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
6493
6651
|
break;
|
|
6494
6652
|
case "invite_named_recipient":
|
|
6495
6653
|
case "grant_named_recipient_access":
|
|
6496
|
-
case "revoke_named_recipient_access":
|
|
6654
|
+
case "revoke_named_recipient_access":
|
|
6655
|
+
case "release_named_recipient": {
|
|
6497
6656
|
if (!seat) throw new Error("named seat effect missing seat");
|
|
6498
|
-
if (effect.kind
|
|
6657
|
+
if (["invite_named_recipient", "grant_named_recipient_access"].includes(String(effect.kind)) && ["refund_pending", "refunded"].includes(String(seat.status))) break;
|
|
6499
6658
|
if (!options.deliverChapterEffect) throw new Error("chapter effect receiver unavailable");
|
|
6500
6659
|
const receipt = await options.deliverChapterEffect({ ...target, effectId: String(effect.id), seatId: String(effect.seatId), kind: effect.kind });
|
|
6501
6660
|
if (receipt.effectId !== effect.id || receipt.complete !== true) throw new Error("chapter effect receipt unconfirmed");
|
|
@@ -6513,7 +6672,7 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
6513
6672
|
pending++;
|
|
6514
6673
|
}
|
|
6515
6674
|
}
|
|
6516
|
-
return { complete, pending, more };
|
|
6675
|
+
return { complete, pending, held, more };
|
|
6517
6676
|
}
|
|
6518
6677
|
|
|
6519
6678
|
// src/schema-membership-offers.ts
|
|
@@ -6683,7 +6842,9 @@ var membershipAuthorityEntities = {
|
|
|
6683
6842
|
...times(),
|
|
6684
6843
|
refundReason: attr9("string", true),
|
|
6685
6844
|
refundedCents: attr9("number", true),
|
|
6686
|
-
providerRefundIds: attr9("json", true)
|
|
6845
|
+
providerRefundIds: attr9("json", true),
|
|
6846
|
+
regiftedAt: attr9("number", true),
|
|
6847
|
+
formerRecipients: attr9("json", true)
|
|
6687
6848
|
} },
|
|
6688
6849
|
networkSeatRecipient: { attrs: { id: id10(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
|
|
6689
6850
|
membershipEffect: { attrs: {
|
|
@@ -6698,6 +6859,8 @@ var membershipAuthorityEntities = {
|
|
|
6698
6859
|
attempts: attr9("number"),
|
|
6699
6860
|
providerObjectId: key2(true),
|
|
6700
6861
|
error: attr9("string", true),
|
|
6862
|
+
recipientUserId: key2(true),
|
|
6863
|
+
recipientApplicationId: key2(true),
|
|
6701
6864
|
createdAt: attr9("number"),
|
|
6702
6865
|
updatedAt: attr9("number", false, false, true)
|
|
6703
6866
|
} }
|
|
@@ -8994,7 +9157,7 @@ var SCHEDULING_DEFAULTS = {
|
|
|
8994
9157
|
endHour: 17,
|
|
8995
9158
|
timezone: "America/Los_Angeles",
|
|
8996
9159
|
minNoticeHours: 24,
|
|
8997
|
-
windowDays:
|
|
9160
|
+
windowDays: 21,
|
|
8998
9161
|
summaryTemplate: "Introduction call with {{firstName}} {{lastName}}"
|
|
8999
9162
|
};
|
|
9000
9163
|
function isValidTimeZone(tz) {
|