@odla-ai/chapter 0.50.0 → 0.51.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 +7 -0
- package/dist/{chunk-OS46ZE46.js → chunk-UAXWA5BL.js} +2 -6
- package/dist/chunk-UAXWA5BL.js.map +1 -0
- package/dist/index.cjs +23 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +23 -11
- package/dist/index.js.map +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/ui/member/index.js +1 -1
- package/dist/worker/index.cjs +71 -14
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +5 -1
- package/dist/worker/index.d.ts +5 -1
- package/dist/worker/index.js +71 -14
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-OS46ZE46.js.map +0 -1
package/dist/worker/index.d.cts
CHANGED
|
@@ -842,7 +842,9 @@ interface MembershipOfferingVersion {
|
|
|
842
842
|
autoRenew: true;
|
|
843
843
|
paymentGraceDays: number;
|
|
844
844
|
seatPriceCents: number;
|
|
845
|
-
|
|
845
|
+
/** Calendar months after the initial payment during which a seat may be bought. Absent: no window; a seat
|
|
846
|
+
* may be bought whenever the primary membership is paid and active, renewals included. */
|
|
847
|
+
seatPurchaseWindowCalendarMonths?: number;
|
|
846
848
|
requiredDisclosures: string[];
|
|
847
849
|
}
|
|
848
850
|
/** Cohort qualification survives the expiration of its monetary benefit. */
|
|
@@ -1010,6 +1012,8 @@ interface NamedSeatOffer {
|
|
|
1010
1012
|
autoRenew: boolean;
|
|
1011
1013
|
eligible: boolean;
|
|
1012
1014
|
digest: string;
|
|
1015
|
+
/** False when the frozen offering sets no purchase window: seats may be bought for as long as the membership is paid. */
|
|
1016
|
+
purchaseWindowed?: boolean;
|
|
1013
1017
|
seat?: {
|
|
1014
1018
|
seatId: string;
|
|
1015
1019
|
status: string;
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -842,7 +842,9 @@ interface MembershipOfferingVersion {
|
|
|
842
842
|
autoRenew: true;
|
|
843
843
|
paymentGraceDays: number;
|
|
844
844
|
seatPriceCents: number;
|
|
845
|
-
|
|
845
|
+
/** Calendar months after the initial payment during which a seat may be bought. Absent: no window; a seat
|
|
846
|
+
* may be bought whenever the primary membership is paid and active, renewals included. */
|
|
847
|
+
seatPurchaseWindowCalendarMonths?: number;
|
|
846
848
|
requiredDisclosures: string[];
|
|
847
849
|
}
|
|
848
850
|
/** Cohort qualification survives the expiration of its monetary benefit. */
|
|
@@ -1010,6 +1012,8 @@ interface NamedSeatOffer {
|
|
|
1010
1012
|
autoRenew: boolean;
|
|
1011
1013
|
eligible: boolean;
|
|
1012
1014
|
digest: string;
|
|
1015
|
+
/** False when the frozen offering sets no purchase window: seats may be bought for as long as the membership is paid. */
|
|
1016
|
+
purchaseWindowed?: boolean;
|
|
1013
1017
|
seat?: {
|
|
1014
1018
|
seatId: string;
|
|
1015
1019
|
status: string;
|
package/dist/worker/index.js
CHANGED
|
@@ -1660,6 +1660,11 @@ function canApprove(status2, p) {
|
|
|
1660
1660
|
}
|
|
1661
1661
|
|
|
1662
1662
|
// src/membership-policy.ts
|
|
1663
|
+
function seatPurchaseDeadline(offering, member) {
|
|
1664
|
+
if (offering.seatPurchaseWindowCalendarMonths === void 0) return Number(member.termEndsAt);
|
|
1665
|
+
if (Number.isSafeInteger(member.seatPurchaseEndsAt)) return Number(member.seatPurchaseEndsAt);
|
|
1666
|
+
return addMembershipCalendarMonths(Number(member.initialTermStartedAt ?? member.termStartedAt), offering.seatPurchaseWindowCalendarMonths);
|
|
1667
|
+
}
|
|
1663
1668
|
var fail2 = (field) => {
|
|
1664
1669
|
throw new TypeError(`invalid membership policy: ${field}`);
|
|
1665
1670
|
};
|
|
@@ -1716,7 +1721,7 @@ function membershipOfferingVersion(value) {
|
|
|
1716
1721
|
autoRenew: true,
|
|
1717
1722
|
paymentGraceDays: integer2(input.paymentGraceDays, 0, 60),
|
|
1718
1723
|
seatPriceCents: integer2(input.seatPriceCents, 1, Number.MAX_SAFE_INTEGER),
|
|
1719
|
-
seatPurchaseWindowCalendarMonths: integer2(input.seatPurchaseWindowCalendarMonths, 1, 120),
|
|
1724
|
+
...input.seatPurchaseWindowCalendarMonths === void 0 || input.seatPurchaseWindowCalendarMonths === null ? {} : { seatPurchaseWindowCalendarMonths: integer2(input.seatPurchaseWindowCalendarMonths, 1, 120) },
|
|
1720
1725
|
requiredDisclosures: ids(input.requiredDisclosures)
|
|
1721
1726
|
};
|
|
1722
1727
|
}
|
|
@@ -2107,7 +2112,7 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
2107
2112
|
initialTermEndsAt: endsAt,
|
|
2108
2113
|
termStartedAt: paidAt,
|
|
2109
2114
|
termEndsAt: endsAt,
|
|
2110
|
-
seatPurchaseEndsAt:
|
|
2115
|
+
seatPurchaseEndsAt: seatPurchaseDeadline(offering, { initialTermStartedAt: paidAt, termEndsAt: endsAt }),
|
|
2111
2116
|
autoRenew: true,
|
|
2112
2117
|
refundedCents: 0,
|
|
2113
2118
|
revision: 1,
|
|
@@ -3642,9 +3647,37 @@ async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
|
|
|
3642
3647
|
return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
|
|
3643
3648
|
}
|
|
3644
3649
|
|
|
3650
|
+
// src/membership-seat-window.ts
|
|
3651
|
+
async function liftSeatPurchaseWindow(db, scope2, now, limit = 100) {
|
|
3652
|
+
membershipClock(now);
|
|
3653
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200) throw new TypeError("invalid lift limit");
|
|
3654
|
+
const state = await loadMembershipAuthorityState(db, scope2);
|
|
3655
|
+
const rows5 = (await db.query({ networkMembership: { $: { where: { ...scope2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkMembership ?? [];
|
|
3656
|
+
let lifted = 0;
|
|
3657
|
+
for (const member of rows5.slice(0, limit)) {
|
|
3658
|
+
if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) throw new Error("membership lift scope mismatch");
|
|
3659
|
+
const offering = membershipOfferingVersion(member.offeringSnapshot);
|
|
3660
|
+
if (offering.seatPurchaseWindowCalendarMonths === void 0) continue;
|
|
3661
|
+
const { seatPurchaseWindowCalendarMonths: _window, ...windowless } = offering;
|
|
3662
|
+
await db.transact([membershipUpdate("networkMembership", String(member.id), {
|
|
3663
|
+
offeringSnapshot: windowless,
|
|
3664
|
+
seatPurchaseEndsAt: Number(member.termEndsAt),
|
|
3665
|
+
revision: Number(member.revision) + 1,
|
|
3666
|
+
updatedAt: now
|
|
3667
|
+
})], { mutationId: `seat-window-lift:${member.id}:${member.revision}`, guards: [
|
|
3668
|
+
{ ns: "networkMembership", id: String(member.id), exists: true, equals: { revision: member.revision } },
|
|
3669
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } }
|
|
3670
|
+
] });
|
|
3671
|
+
lifted++;
|
|
3672
|
+
}
|
|
3673
|
+
return { lifted, more: rows5.length > limit };
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3645
3676
|
// src/worker-routes-admin-membership-seats.ts
|
|
3646
3677
|
var LIST = "/api/admin/membership-seats";
|
|
3678
|
+
var LIFT = "/api/admin/membership-seats/lift-purchase-window";
|
|
3647
3679
|
var ONE = /^\/api\/admin\/membership-seats\/([A-Za-z0-9][A-Za-z0-9_.:-]{2,159})\/refund$/;
|
|
3680
|
+
var SCOPE = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
3648
3681
|
var PUBLIC = [
|
|
3649
3682
|
"id",
|
|
3650
3683
|
"appId",
|
|
@@ -3669,11 +3702,11 @@ var PUBLIC = [
|
|
|
3669
3702
|
];
|
|
3670
3703
|
var project = (row2) => Object.fromEntries(PUBLIC.filter((key3) => row2[key3] !== void 0).map((key3) => [key3, row2[key3]]));
|
|
3671
3704
|
var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
3672
|
-
const one = url.pathname.match(ONE);
|
|
3673
|
-
if (url.pathname !== LIST && !one) return null;
|
|
3705
|
+
const one = url.pathname.match(ONE), lift = url.pathname === LIFT;
|
|
3706
|
+
if (url.pathname !== LIST && !one && !lift) return null;
|
|
3674
3707
|
const operator = ctx.membershipAuthorityOperator;
|
|
3675
3708
|
if (!operator) return json({ error: "not found" }, 404);
|
|
3676
|
-
if (req.method !== (one ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
|
|
3709
|
+
if (req.method !== (one || lift ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
|
|
3677
3710
|
const raw2 = ctx.makeDb(env), user = await ctx.verifyUser(req, env);
|
|
3678
3711
|
if (!user) return json({ error: "unauthorized" }, 401);
|
|
3679
3712
|
if (!await ctx.isSuperAdminEmail(raw2, user.email, user.userId)) return json({ error: "super-admin required", code: "super_admin_required" }, 403);
|
|
@@ -3683,6 +3716,24 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
|
3683
3716
|
} catch {
|
|
3684
3717
|
return json({ error: "the membership authority is not hosted by this deployment" }, 404);
|
|
3685
3718
|
}
|
|
3719
|
+
if (lift) {
|
|
3720
|
+
let body;
|
|
3721
|
+
try {
|
|
3722
|
+
body = await req.json();
|
|
3723
|
+
} catch {
|
|
3724
|
+
return json({ error: "chapterId and runtime are required" }, 400);
|
|
3725
|
+
}
|
|
3726
|
+
const appId = body.appId ?? body.chapterId;
|
|
3727
|
+
if (typeof body.chapterId !== "string" || !SCOPE.test(body.chapterId) || typeof appId !== "string" || !SCOPE.test(appId) || typeof body.runtime !== "string" || !/^[a-z0-9]{2,12}$/.test(body.runtime)) {
|
|
3728
|
+
return json({ error: "chapterId and runtime are required" }, 400);
|
|
3729
|
+
}
|
|
3730
|
+
try {
|
|
3731
|
+
return json({ ok: true, ...await liftSeatPurchaseWindow(db, { appId, chapterId: body.chapterId, environment: env.ODLA_ENV, runtime: body.runtime }, Date.now()) });
|
|
3732
|
+
} catch (error) {
|
|
3733
|
+
const code = error.code;
|
|
3734
|
+
return json({ error: "purchase window lift is pending; retry to confirm", ...code ? { code } : {} }, code ? 409 : 503);
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3686
3737
|
if (!one) {
|
|
3687
3738
|
const rows5 = (await db.query({ networkSeat: { $: { where: { environment: env.ODLA_ENV }, order: { updatedAt: "desc" }, limit: 201 } } })).networkSeat ?? [];
|
|
3688
3739
|
return json({ seats: rows5.slice(0, 200).map(project), truncated: rows5.length > 200 });
|
|
@@ -8730,15 +8781,16 @@ async function readNamedSeatOffer(db, input, now) {
|
|
|
8730
8781
|
const offering = membershipOfferingVersion(member.offeringSnapshot);
|
|
8731
8782
|
const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
|
|
8732
8783
|
if (seat) requireMembershipScope(seat, input);
|
|
8784
|
+
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
8733
8785
|
const offer = {
|
|
8734
8786
|
membershipId: id6,
|
|
8735
8787
|
amountCents: offering.seatPriceCents,
|
|
8736
8788
|
currency: offering.currency,
|
|
8737
8789
|
interval: offering.interval,
|
|
8738
8790
|
termEndsAt: Number(member.termEndsAt),
|
|
8739
|
-
purchaseEndsAt
|
|
8791
|
+
purchaseEndsAt,
|
|
8740
8792
|
autoRenew: member.autoRenew === true,
|
|
8741
|
-
eligible: ["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) && member.refundedCents === 0 && now >= Number(member.initialTermStartedAt) && now < Number(member.termEndsAt) && now <=
|
|
8793
|
+
eligible: ["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) && member.refundedCents === 0 && now >= Number(member.initialTermStartedAt) && now < Number(member.termEndsAt) && now <= purchaseEndsAt && (!seat || ["payment_pending", "payment_processing"].includes(String(seat.status))),
|
|
8742
8794
|
...seat ? { seat: {
|
|
8743
8795
|
seatId: String(seat.id),
|
|
8744
8796
|
status: String(seat.status),
|
|
@@ -8746,7 +8798,7 @@ async function readNamedSeatOffer(db, input, now) {
|
|
|
8746
8798
|
recipientEmail: String(seat.recipientEmailNormalized)
|
|
8747
8799
|
} } : {}
|
|
8748
8800
|
};
|
|
8749
|
-
return { ...offer, digest: await namedSeatOfferDigest(offer) };
|
|
8801
|
+
return { ...offer, digest: await namedSeatOfferDigest(offer), purchaseWindowed: offering.seatPurchaseWindowCalendarMonths !== void 0 };
|
|
8750
8802
|
}
|
|
8751
8803
|
|
|
8752
8804
|
// src/named-seat-request.ts
|
|
@@ -13763,7 +13815,11 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
13763
13815
|
membershipUpdate("networkMembership", id6, {
|
|
13764
13816
|
status: status2,
|
|
13765
13817
|
graceEndsAt,
|
|
13766
|
-
...paid ? {
|
|
13818
|
+
...paid ? {
|
|
13819
|
+
termStartedAt: start,
|
|
13820
|
+
termEndsAt: end,
|
|
13821
|
+
...membershipOfferingVersion(member.offeringSnapshot).seatPurchaseWindowCalendarMonths === void 0 ? { seatPurchaseEndsAt: end } : {}
|
|
13822
|
+
} : {},
|
|
13767
13823
|
revision: Number(member.revision) + 1,
|
|
13768
13824
|
updatedAt: now
|
|
13769
13825
|
}),
|
|
@@ -13923,10 +13979,11 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
13923
13979
|
const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
13924
13980
|
let seat = await membershipLoad(db, "networkSeat", id6);
|
|
13925
13981
|
if (!seat) {
|
|
13926
|
-
|
|
13982
|
+
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
|
|
13983
|
+
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
13984
|
+
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) {
|
|
13927
13985
|
membershipFail("named_seat_purchase_unavailable");
|
|
13928
13986
|
}
|
|
13929
|
-
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
|
|
13930
13987
|
const attrs = {
|
|
13931
13988
|
id: id6,
|
|
13932
13989
|
...membershipScope(input),
|
|
@@ -13945,7 +14002,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
13945
14002
|
status: "payment_pending",
|
|
13946
14003
|
preparedAt: now,
|
|
13947
14004
|
termEndsAt: member.termEndsAt,
|
|
13948
|
-
seatPurchaseEndsAt:
|
|
14005
|
+
seatPurchaseEndsAt: purchaseEndsAt,
|
|
13949
14006
|
offeringSnapshot: offering,
|
|
13950
14007
|
revision: 1,
|
|
13951
14008
|
createdAt: now,
|
|
@@ -14147,7 +14204,7 @@ async function confirmNamedMembershipSeatPayment(db, input, now) {
|
|
|
14147
14204
|
}
|
|
14148
14205
|
if (seat.purchasedAt === void 0) {
|
|
14149
14206
|
if (!["payment_processing", "payment_canceling"].includes(String(seat.status))) membershipFail("named_seat_payment_not_expected");
|
|
14150
|
-
const eligible = seat.status !== "payment_canceling" && ["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) && Number(member.refundedCents) === 0 && paidAt <=
|
|
14207
|
+
const eligible = seat.status !== "payment_canceling" && ["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) && Number(member.refundedCents) === 0 && paidAt <= seatPurchaseDeadline(membershipOfferingVersion(member.offeringSnapshot), member) && now < Number(member.termEndsAt);
|
|
14151
14208
|
const status2 = eligible ? "renewal_pending" : "refund_pending", effectId = `${seat.id}:${eligible ? "attach-renewal" : "refund"}`;
|
|
14152
14209
|
try {
|
|
14153
14210
|
await db.transact([
|
|
@@ -15366,7 +15423,7 @@ async function requestNamedSeatReservation(options) {
|
|
|
15366
15423
|
async function requestNamedSeatOffer(options) {
|
|
15367
15424
|
const result = await requestAuthority(options, "seat-offer", { applicationId: options.applicationId, skuId: options.skuId });
|
|
15368
15425
|
const offer = result.seat;
|
|
15369
|
-
if (!offer || offer.membershipId !== await networkMembershipId(options) || !Number.isSafeInteger(offer.amountCents) || offer.amountCents <= 0 || !/^[a-z]{3}$/.test(offer.currency) || !["month", "year"].includes(offer.interval) || typeof offer.eligible !== "boolean" || typeof offer.autoRenew !== "boolean" || !Number.isSafeInteger(offer.termEndsAt) || !Number.isSafeInteger(offer.purchaseEndsAt) || offer.digest !== await namedSeatOfferDigest(offer)) throw new Error("invalid named seat offer");
|
|
15426
|
+
if (!offer || offer.membershipId !== await networkMembershipId(options) || !Number.isSafeInteger(offer.amountCents) || offer.amountCents <= 0 || !/^[a-z]{3}$/.test(offer.currency) || !["month", "year"].includes(offer.interval) || typeof offer.eligible !== "boolean" || typeof offer.autoRenew !== "boolean" || !Number.isSafeInteger(offer.termEndsAt) || !Number.isSafeInteger(offer.purchaseEndsAt) || offer.purchaseWindowed !== void 0 && typeof offer.purchaseWindowed !== "boolean" || offer.digest !== await namedSeatOfferDigest(offer)) throw new Error("invalid named seat offer");
|
|
15370
15427
|
return offer;
|
|
15371
15428
|
}
|
|
15372
15429
|
async function requestNamedSeatInvitation(options) {
|