@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
package/dist/index.js
CHANGED
|
@@ -139,6 +139,7 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
139
139
|
clerkUserId: opts.namedSeat.userId,
|
|
140
140
|
clerkAccountPending: false
|
|
141
141
|
});
|
|
142
|
+
if (opts.predecessor?.clerkUserId && !opts.namedSeat) Object.assign(row2, { clerkUserId: opts.predecessor.clerkUserId, clerkAccountPending: false });
|
|
142
143
|
if (typeof fields.email === "string" && isValidEmail(fields.email.trim())) {
|
|
143
144
|
row2.emailNormalized = normalizeChapterEmail(fields.email);
|
|
144
145
|
row2.signupEmailKey = row2.emailNormalized;
|
|
@@ -157,6 +158,8 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
157
158
|
}
|
|
158
159
|
if (acked) row2.disclaimerAckAt = opts.now;
|
|
159
160
|
const ops = [
|
|
161
|
+
// The unique signup key leaves the closed predecessor before the new attempt takes it.
|
|
162
|
+
...opts.predecessor && opts.predecessor.id !== id11 ? [{ t: "retract", ns: "applications", id: opts.predecessor.id, attrs: ["signupEmailKey"] }] : [],
|
|
160
163
|
{ t: "update", ns: "applications", id: id11, attrs: row2 }
|
|
161
164
|
];
|
|
162
165
|
if (opts.admissionGrant?.decision === "waive_interview") {
|
|
@@ -1093,7 +1096,7 @@ var DEFAULT_SCHEDULING = {
|
|
|
1093
1096
|
endHour: 17,
|
|
1094
1097
|
timezone: "America/Los_Angeles",
|
|
1095
1098
|
minNoticeHours: 24,
|
|
1096
|
-
windowDays:
|
|
1099
|
+
windowDays: 21
|
|
1097
1100
|
};
|
|
1098
1101
|
function defaultEmailTemplates(name) {
|
|
1099
1102
|
const sign = `
|
|
@@ -1492,8 +1495,10 @@ var DEFAULT_ADMIN_COPY = {
|
|
|
1492
1495
|
superAdminHint: "Super-admin, managed in odla Studio.",
|
|
1493
1496
|
noAccount: "No linked account yet. The role is set once they sign in.",
|
|
1494
1497
|
approve: "Approve",
|
|
1498
|
+
decline: "Decline",
|
|
1495
1499
|
refund: "Refund",
|
|
1496
1500
|
approved: "Approved.",
|
|
1501
|
+
declined: "Declined.",
|
|
1497
1502
|
refunded: "Refund issued.",
|
|
1498
1503
|
roleSet: "Role set to {role}.",
|
|
1499
1504
|
messageSent: "Message sent.",
|
|
@@ -4273,7 +4278,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
|
|
|
4273
4278
|
import { stripeRequest as stripeRequest4 } from "@odla-ai/stripe";
|
|
4274
4279
|
|
|
4275
4280
|
// src/membership-seat-reservation.ts
|
|
4276
|
-
async function
|
|
4281
|
+
async function namedSeatRecipientClaimId(input, email) {
|
|
4277
4282
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([input.appId, input.chapterId, input.environment, input.runtime, email])));
|
|
4278
4283
|
return `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
4279
4284
|
}
|
|
@@ -4290,7 +4295,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
4290
4295
|
const id11 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
4291
4296
|
let seat = await membershipLoad(db, "networkSeat", id11);
|
|
4292
4297
|
if (!seat) {
|
|
4293
|
-
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await
|
|
4298
|
+
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await namedSeatRecipientClaimId(input, email);
|
|
4294
4299
|
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
4295
4300
|
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) {
|
|
4296
4301
|
membershipFail("named_seat_purchase_unavailable");
|
|
@@ -5379,6 +5384,128 @@ async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
|
|
|
5379
5384
|
return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
|
|
5380
5385
|
}
|
|
5381
5386
|
|
|
5387
|
+
// src/membership-effect-kinds.ts
|
|
5388
|
+
var MEMBERSHIP_CHAPTER_EFFECT_KINDS = [
|
|
5389
|
+
"invite_named_recipient",
|
|
5390
|
+
"grant_named_recipient_access",
|
|
5391
|
+
"revoke_named_recipient_access",
|
|
5392
|
+
"release_named_recipient"
|
|
5393
|
+
];
|
|
5394
|
+
var namedSeatInvitationOpen = (member) => ["active", "cancel_at_term_end"].includes(String(member.status)) && Number.isSafeInteger(member.journeyApprovedAt);
|
|
5395
|
+
|
|
5396
|
+
// src/membership-seat-regift.ts
|
|
5397
|
+
var REGIFTABLE_SEAT_STATUSES = ["pending_acceptance", "pending_approval", "pending_primary"];
|
|
5398
|
+
var namedSeatRegiftable = (seat) => REGIFTABLE_SEAT_STATUSES.includes(String(seat.status));
|
|
5399
|
+
var receiptFor = (seat, released) => ({
|
|
5400
|
+
seatId: String(seat.id),
|
|
5401
|
+
status: "pending_acceptance",
|
|
5402
|
+
recipientName: String(seat.recipientName),
|
|
5403
|
+
recipientEmail: String(seat.recipientEmailNormalized),
|
|
5404
|
+
regiftedAt: Number(seat.regiftedAt),
|
|
5405
|
+
releasedRecipientApplicationId: released
|
|
5406
|
+
});
|
|
5407
|
+
async function regiftNamedSeat(db, input, now) {
|
|
5408
|
+
membershipClock(now);
|
|
5409
|
+
const name = typeof input.recipientName === "string" ? input.recipientName.trim().replace(/\s+/g, " ") : "";
|
|
5410
|
+
const email = normalizeChapterEmail(typeof input.recipientEmail === "string" ? input.recipientEmail : "");
|
|
5411
|
+
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");
|
|
5412
|
+
const state = await loadMembershipAuthorityState(db, input);
|
|
5413
|
+
const seat = await membershipLoad(db, "networkSeat", input.seatId);
|
|
5414
|
+
requireMembershipScope(seat, input);
|
|
5415
|
+
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
5416
|
+
requireMembershipScope(member, input);
|
|
5417
|
+
if (member.namedSeatId !== seat.id) membershipFail("named_seat_primary_conflict");
|
|
5418
|
+
if (typeof seat.purchaserEmailNormalized === "string" && seat.purchaserEmailNormalized === email) membershipFail("invalid_named_seat_recipient");
|
|
5419
|
+
const history = Array.isArray(seat.formerRecipients) ? seat.formerRecipients : [];
|
|
5420
|
+
if (seat.recipientEmailNormalized === email && seat.recipientName === name && seat.regiftedAt !== void 0 && seat.status === "pending_acceptance") {
|
|
5421
|
+
return receiptFor(seat, history.at(-1)?.recipientApplicationId ?? null);
|
|
5422
|
+
}
|
|
5423
|
+
if (!namedSeatRegiftable(seat)) membershipFail("named_seat_not_regiftable");
|
|
5424
|
+
if (seat.recipientEmailNormalized === email) membershipFail("named_seat_recipient_unchanged");
|
|
5425
|
+
const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
|
|
5426
|
+
requireMembershipScope(entitlement, input);
|
|
5427
|
+
const claimId = await namedSeatRecipientClaimId(input, email);
|
|
5428
|
+
const former = {
|
|
5429
|
+
recipientName: String(seat.recipientName),
|
|
5430
|
+
recipientEmail: String(seat.recipientEmailNormalized),
|
|
5431
|
+
recipientClaimId: String(seat.recipientClaimId),
|
|
5432
|
+
releasedAt: now,
|
|
5433
|
+
...typeof seat.recipientUserId === "string" ? { recipientUserId: seat.recipientUserId } : {},
|
|
5434
|
+
...typeof seat.recipientApplicationId === "string" ? { recipientApplicationId: seat.recipientApplicationId } : {},
|
|
5435
|
+
...Number.isSafeInteger(seat.acceptedAt) ? { acceptedAt: Number(seat.acceptedAt) } : {},
|
|
5436
|
+
...Number.isSafeInteger(seat.journeyApprovedAt) ? { journeyApprovedAt: Number(seat.journeyApprovedAt) } : {}
|
|
5437
|
+
};
|
|
5438
|
+
const generation = history.length + 1, inviteId = `${seat.id}:invite:${generation}`, releaseId = `${seat.id}:release:${generation}`;
|
|
5439
|
+
const bound = Boolean(former.recipientUserId && former.recipientApplicationId);
|
|
5440
|
+
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);
|
|
5441
|
+
const cleared = ["recipientUserId", "recipientApplicationId", "acceptedAt", "journeyApprovedAt"];
|
|
5442
|
+
const effect = (id11, kind, extra = {}) => membershipUpdate("membershipEffect", id11, {
|
|
5443
|
+
id: id11,
|
|
5444
|
+
chapterId: input.chapterId,
|
|
5445
|
+
environment: input.environment,
|
|
5446
|
+
runtime: input.runtime,
|
|
5447
|
+
membershipId: member.id,
|
|
5448
|
+
seatId: seat.id,
|
|
5449
|
+
kind,
|
|
5450
|
+
status: "pending",
|
|
5451
|
+
attempts: 0,
|
|
5452
|
+
createdAt: now,
|
|
5453
|
+
updatedAt: now,
|
|
5454
|
+
...extra
|
|
5455
|
+
});
|
|
5456
|
+
const ops = [
|
|
5457
|
+
membershipUpdate("networkSeat", String(seat.id), {
|
|
5458
|
+
recipientName: name,
|
|
5459
|
+
recipientEmailNormalized: email,
|
|
5460
|
+
recipientClaimId: claimId,
|
|
5461
|
+
status: "pending_acceptance",
|
|
5462
|
+
regiftedAt: now,
|
|
5463
|
+
formerRecipients: [...history, former],
|
|
5464
|
+
revision: Number(seat.revision) + 1,
|
|
5465
|
+
updatedAt: now
|
|
5466
|
+
}),
|
|
5467
|
+
{ t: "retract", ns: "networkSeat", id: String(seat.id), attrs: cleared },
|
|
5468
|
+
membershipUpdate("networkEntitlement", String(entitlement.id), { status: "pending_acceptance", revision: Number(entitlement.revision) + 1, updatedAt: now }),
|
|
5469
|
+
{ t: "retract", ns: "networkEntitlement", id: String(entitlement.id), attrs: cleared },
|
|
5470
|
+
{ t: "delete", ns: "networkSeatRecipient", id: String(seat.recipientClaimId) },
|
|
5471
|
+
membershipUpdate("networkSeatRecipient", claimId, {
|
|
5472
|
+
id: claimId,
|
|
5473
|
+
appId: input.appId,
|
|
5474
|
+
chapterId: input.chapterId,
|
|
5475
|
+
environment: input.environment,
|
|
5476
|
+
runtime: input.runtime,
|
|
5477
|
+
seatId: seat.id,
|
|
5478
|
+
recipientEmailNormalized: email,
|
|
5479
|
+
createdAt: now
|
|
5480
|
+
}),
|
|
5481
|
+
...stale.map((row2) => membershipUpdate("membershipEffect", String(row2.id), { status: "complete", updatedAt: now })),
|
|
5482
|
+
effect(inviteId, "invite_named_recipient"),
|
|
5483
|
+
...bound ? [effect(releaseId, "release_named_recipient", { recipientUserId: former.recipientUserId, recipientApplicationId: former.recipientApplicationId })] : []
|
|
5484
|
+
];
|
|
5485
|
+
try {
|
|
5486
|
+
await db.transact(ops, { mutationId: `named-seat-regift:${seat.id}:${generation}`, guards: [
|
|
5487
|
+
{ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision, status: seat.status } },
|
|
5488
|
+
{ ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } },
|
|
5489
|
+
{ ns: "networkMembership", id: String(member.id), exists: true, equals: { revision: member.revision, namedSeatId: seat.id } },
|
|
5490
|
+
{ ns: "networkSeatRecipient", id: claimId, exists: false },
|
|
5491
|
+
...stale.map((row2) => ({ ns: "membershipEffect", id: String(row2.id), exists: true, equals: { status: "pending" } })),
|
|
5492
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
5493
|
+
] });
|
|
5494
|
+
} catch (error) {
|
|
5495
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
5496
|
+
const current = await membershipLoad(db, "networkSeat", input.seatId);
|
|
5497
|
+
requireMembershipScope(current, input);
|
|
5498
|
+
if (current.recipientEmailNormalized === email && current.status === "pending_acceptance") return receiptFor(current, former.recipientApplicationId ?? null);
|
|
5499
|
+
const claim = await membershipLoad(db, "networkSeatRecipient", claimId);
|
|
5500
|
+
if (claim && claim.seatId !== seat.id) membershipFail("named_seat_recipient_unavailable");
|
|
5501
|
+
membershipFail("named_seat_regift_retry_required");
|
|
5502
|
+
}
|
|
5503
|
+
const saved = await membershipLoad(db, "networkSeat", input.seatId);
|
|
5504
|
+
requireMembershipScope(saved, input);
|
|
5505
|
+
if (saved.recipientEmailNormalized !== email || saved.status !== "pending_acceptance") membershipFail("named_seat_regift_retry_required");
|
|
5506
|
+
return receiptFor(saved, former.recipientApplicationId ?? null);
|
|
5507
|
+
}
|
|
5508
|
+
|
|
5382
5509
|
// src/membership-seat-offer.ts
|
|
5383
5510
|
async function namedSeatOfferDigest(offer) {
|
|
5384
5511
|
const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
|
|
@@ -5422,7 +5549,10 @@ async function readNamedSeatOffer(db, input, now) {
|
|
|
5422
5549
|
recipientState: namedSeatRecipientState(seat),
|
|
5423
5550
|
...at(seat.purchasedAt) !== void 0 ? { purchasedAt: at(seat.purchasedAt) } : {},
|
|
5424
5551
|
...at(seat.acceptedAt) !== void 0 ? { acceptedAt: at(seat.acceptedAt) } : {},
|
|
5425
|
-
...seat.status === "active" && at(seat.journeyApprovedAt) !== void 0 ? { joinedAt: at(seat.journeyApprovedAt) } : {}
|
|
5552
|
+
...seat.status === "active" && at(seat.journeyApprovedAt) !== void 0 ? { joinedAt: at(seat.journeyApprovedAt) } : {},
|
|
5553
|
+
...seat.status === "pending_acceptance" && seat.acceptedAt === void 0 && !namedSeatInvitationOpen(member) ? { invitationHeld: true } : {},
|
|
5554
|
+
...namedSeatRegiftable(seat) ? { regiftable: true } : {},
|
|
5555
|
+
...at(seat.regiftedAt) !== void 0 ? { regiftedAt: at(seat.regiftedAt) } : {}
|
|
5426
5556
|
} } : {}
|
|
5427
5557
|
};
|
|
5428
5558
|
return {
|
|
@@ -5470,10 +5600,14 @@ async function readMembershipChapterEffect(db, input, now) {
|
|
|
5470
5600
|
requireMembershipScope(seat, input);
|
|
5471
5601
|
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
5472
5602
|
requireMembershipScope(member, input);
|
|
5473
|
-
if (!effect || effect.kind !== input.kind || effect.seatId !== seat.id || effect.membershipId !== member.id || ["chapterId", "environment", "runtime"].some((key3) => effect[key3] !== seat[key3]) || !
|
|
5603
|
+
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");
|
|
5604
|
+
const released = input.kind === "release_named_recipient";
|
|
5605
|
+
const recipientUserId = released ? effect.recipientUserId : seat.recipientUserId;
|
|
5606
|
+
const recipientApplicationId = released ? effect.recipientApplicationId : seat.recipientApplicationId;
|
|
5474
5607
|
let applicable;
|
|
5475
|
-
if (input.kind === "invite_named_recipient") applicable = seat.status === "pending_acceptance" && seat.acceptedAt === void 0 && now < Number(member.termEndsAt) &&
|
|
5608
|
+
if (input.kind === "invite_named_recipient") applicable = seat.status === "pending_acceptance" && seat.acceptedAt === void 0 && now < Number(member.termEndsAt) && namedSeatInvitationOpen(member);
|
|
5476
5609
|
else if (input.kind === "revoke_named_recipient_access") applicable = ["refund_pending", "refunded"].includes(String(seat.status));
|
|
5610
|
+
else if (released) applicable = typeof recipientApplicationId === "string" && seat.recipientApplicationId !== recipientApplicationId;
|
|
5477
5611
|
else applicable = (await readNamedSeatAccess(db, {
|
|
5478
5612
|
...input,
|
|
5479
5613
|
recipientUserId: String(seat.recipientUserId),
|
|
@@ -5483,8 +5617,8 @@ async function readMembershipChapterEffect(db, input, now) {
|
|
|
5483
5617
|
...input,
|
|
5484
5618
|
recipientName: String(seat.recipientName),
|
|
5485
5619
|
recipientEmail: String(seat.recipientEmailNormalized),
|
|
5486
|
-
recipientUserId: typeof
|
|
5487
|
-
recipientApplicationId: typeof
|
|
5620
|
+
recipientUserId: typeof recipientUserId === "string" ? recipientUserId : null,
|
|
5621
|
+
recipientApplicationId: typeof recipientApplicationId === "string" ? recipientApplicationId : null,
|
|
5488
5622
|
acceptanceEndsAt: Number(seat.acceptanceEndsAt),
|
|
5489
5623
|
termEndsAt: Number(seat.termEndsAt),
|
|
5490
5624
|
applicable
|
|
@@ -5505,7 +5639,8 @@ var MEMBERSHIP_SEAT_ACTION_FIELDS = {
|
|
|
5505
5639
|
"seat-accept": recipient,
|
|
5506
5640
|
"seat-approve": recipient,
|
|
5507
5641
|
"seat-decline": recipient,
|
|
5508
|
-
"seat-effect": ["seatId", "effectId", "kind"]
|
|
5642
|
+
"seat-effect": ["seatId", "effectId", "kind"],
|
|
5643
|
+
"seat-regift": ["seatId", "recipientName", "recipientEmail"]
|
|
5509
5644
|
};
|
|
5510
5645
|
async function handleMembershipSeatAction(action, db, peer, input, now, stripeSecretName) {
|
|
5511
5646
|
if (!peer.seatPriceId) throw new Error("named seats are not configured");
|
|
@@ -5530,6 +5665,12 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
|
|
|
5530
5665
|
kind: String(input.kind)
|
|
5531
5666
|
}, now);
|
|
5532
5667
|
if (action === "seat-offer") return readNamedSeatOffer(db, target, now);
|
|
5668
|
+
if (action === "seat-regift") return regiftNamedSeat(db, {
|
|
5669
|
+
...target,
|
|
5670
|
+
seatId: String(input.seatId),
|
|
5671
|
+
recipientName: String(input.recipientName),
|
|
5672
|
+
recipientEmail: String(input.recipientEmail)
|
|
5673
|
+
}, now);
|
|
5533
5674
|
if (action === "seat-reservations") {
|
|
5534
5675
|
const seat = await reserveNamedMembershipSeat(db, {
|
|
5535
5676
|
...target,
|
|
@@ -5907,6 +6048,18 @@ async function requestNamedSeatOperatorRefund(options) {
|
|
|
5907
6048
|
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");
|
|
5908
6049
|
return seat;
|
|
5909
6050
|
}
|
|
6051
|
+
async function requestNamedSeatRegift(options) {
|
|
6052
|
+
const result = await requestAuthority(options, "seat-regift", {
|
|
6053
|
+
applicationId: options.applicationId,
|
|
6054
|
+
skuId: options.skuId,
|
|
6055
|
+
seatId: options.seatId,
|
|
6056
|
+
recipientName: options.recipientName,
|
|
6057
|
+
recipientEmail: options.recipientEmail
|
|
6058
|
+
});
|
|
6059
|
+
const seat = result.seat;
|
|
6060
|
+
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");
|
|
6061
|
+
return seat;
|
|
6062
|
+
}
|
|
5910
6063
|
async function requestNamedSeatReservation(options) {
|
|
5911
6064
|
const result = await requestAuthority(options, "seat-reservations", {
|
|
5912
6065
|
applicationId: options.applicationId,
|
|
@@ -6152,7 +6305,7 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
6152
6305
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !options.stripeSecretKey) throw new TypeError("invalid membership recovery configuration");
|
|
6153
6306
|
await loadMembershipAuthorityState(db, scope2);
|
|
6154
6307
|
const matches2 = (row2) => Object.entries(scope2).every(([key3, value]) => row2[key3] === value);
|
|
6155
|
-
let pending = 0, complete = 0, more = false;
|
|
6308
|
+
let pending = 0, complete = 0, held = 0, more = false;
|
|
6156
6309
|
for (const status2 of ["payment_pending", "payment_processing", "payment_canceling"]) {
|
|
6157
6310
|
const seats = (await db.query({ networkSeat: { $: { where: { ...scope2, status: status2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkSeat ?? [];
|
|
6158
6311
|
more ||= seats.length > limit;
|
|
@@ -6188,6 +6341,10 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
6188
6341
|
requireMembershipScope(seat, target);
|
|
6189
6342
|
if (seat.primaryMembershipId !== member.id) throw new Error("membership seat scope mismatch");
|
|
6190
6343
|
}
|
|
6344
|
+
if (effect.kind === "invite_named_recipient" && seat?.acceptedAt === void 0 && !namedSeatInvitationOpen(member) && !["refund_pending", "refunded"].includes(String(seat?.status))) {
|
|
6345
|
+
held++;
|
|
6346
|
+
continue;
|
|
6347
|
+
}
|
|
6191
6348
|
try {
|
|
6192
6349
|
const attempts = Number(effect.attempts);
|
|
6193
6350
|
if (!Number.isSafeInteger(attempts) || attempts < 0) throw new Error("invalid membership effect attempt");
|
|
@@ -6211,9 +6368,10 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
6211
6368
|
break;
|
|
6212
6369
|
case "invite_named_recipient":
|
|
6213
6370
|
case "grant_named_recipient_access":
|
|
6214
|
-
case "revoke_named_recipient_access":
|
|
6371
|
+
case "revoke_named_recipient_access":
|
|
6372
|
+
case "release_named_recipient": {
|
|
6215
6373
|
if (!seat) throw new Error("named seat effect missing seat");
|
|
6216
|
-
if (effect.kind
|
|
6374
|
+
if (["invite_named_recipient", "grant_named_recipient_access"].includes(String(effect.kind)) && ["refund_pending", "refunded"].includes(String(seat.status))) break;
|
|
6217
6375
|
if (!options.deliverChapterEffect) throw new Error("chapter effect receiver unavailable");
|
|
6218
6376
|
const receipt = await options.deliverChapterEffect({ ...target, effectId: String(effect.id), seatId: String(effect.seatId), kind: effect.kind });
|
|
6219
6377
|
if (receipt.effectId !== effect.id || receipt.complete !== true) throw new Error("chapter effect receipt unconfirmed");
|
|
@@ -6231,7 +6389,7 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
6231
6389
|
pending++;
|
|
6232
6390
|
}
|
|
6233
6391
|
}
|
|
6234
|
-
return { complete, pending, more };
|
|
6392
|
+
return { complete, pending, held, more };
|
|
6235
6393
|
}
|
|
6236
6394
|
|
|
6237
6395
|
// src/schema-membership-offers.ts
|
|
@@ -6401,7 +6559,9 @@ var membershipAuthorityEntities = {
|
|
|
6401
6559
|
...times(),
|
|
6402
6560
|
refundReason: attr9("string", true),
|
|
6403
6561
|
refundedCents: attr9("number", true),
|
|
6404
|
-
providerRefundIds: attr9("json", true)
|
|
6562
|
+
providerRefundIds: attr9("json", true),
|
|
6563
|
+
regiftedAt: attr9("number", true),
|
|
6564
|
+
formerRecipients: attr9("json", true)
|
|
6405
6565
|
} },
|
|
6406
6566
|
networkSeatRecipient: { attrs: { id: id10(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
|
|
6407
6567
|
membershipEffect: { attrs: {
|
|
@@ -6416,6 +6576,8 @@ var membershipAuthorityEntities = {
|
|
|
6416
6576
|
attempts: attr9("number"),
|
|
6417
6577
|
providerObjectId: key2(true),
|
|
6418
6578
|
error: attr9("string", true),
|
|
6579
|
+
recipientUserId: key2(true),
|
|
6580
|
+
recipientApplicationId: key2(true),
|
|
6419
6581
|
createdAt: attr9("number"),
|
|
6420
6582
|
updatedAt: attr9("number", false, false, true)
|
|
6421
6583
|
} }
|
|
@@ -8717,7 +8879,7 @@ var SCHEDULING_DEFAULTS = {
|
|
|
8717
8879
|
endHour: 17,
|
|
8718
8880
|
timezone: "America/Los_Angeles",
|
|
8719
8881
|
minNoticeHours: 24,
|
|
8720
|
-
windowDays:
|
|
8882
|
+
windowDays: 21,
|
|
8721
8883
|
summaryTemplate: "Introduction call with {{firstName}} {{lastName}}"
|
|
8722
8884
|
};
|
|
8723
8885
|
function isValidTimeZone(tz) {
|
|
@@ -10220,6 +10382,7 @@ export {
|
|
|
10220
10382
|
requestNamedSeatOperatorRefund,
|
|
10221
10383
|
requestNamedSeatPayment,
|
|
10222
10384
|
requestNamedSeatRefund2 as requestNamedSeatRefund,
|
|
10385
|
+
requestNamedSeatRegift,
|
|
10223
10386
|
requestNamedSeatReservation,
|
|
10224
10387
|
resolveApplication,
|
|
10225
10388
|
resolveAuth,
|