@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/ui/index.js
CHANGED
package/dist/ui/member/index.js
CHANGED
package/dist/worker/index.cjs
CHANGED
|
@@ -1702,6 +1702,11 @@ function canApprove(status2, p) {
|
|
|
1702
1702
|
}
|
|
1703
1703
|
|
|
1704
1704
|
// src/membership-policy.ts
|
|
1705
|
+
function seatPurchaseDeadline(offering, member) {
|
|
1706
|
+
if (offering.seatPurchaseWindowCalendarMonths === void 0) return Number(member.termEndsAt);
|
|
1707
|
+
if (Number.isSafeInteger(member.seatPurchaseEndsAt)) return Number(member.seatPurchaseEndsAt);
|
|
1708
|
+
return addMembershipCalendarMonths(Number(member.initialTermStartedAt ?? member.termStartedAt), offering.seatPurchaseWindowCalendarMonths);
|
|
1709
|
+
}
|
|
1705
1710
|
var fail2 = (field) => {
|
|
1706
1711
|
throw new TypeError(`invalid membership policy: ${field}`);
|
|
1707
1712
|
};
|
|
@@ -1758,7 +1763,7 @@ function membershipOfferingVersion(value) {
|
|
|
1758
1763
|
autoRenew: true,
|
|
1759
1764
|
paymentGraceDays: integer2(input.paymentGraceDays, 0, 60),
|
|
1760
1765
|
seatPriceCents: integer2(input.seatPriceCents, 1, Number.MAX_SAFE_INTEGER),
|
|
1761
|
-
seatPurchaseWindowCalendarMonths: integer2(input.seatPurchaseWindowCalendarMonths, 1, 120),
|
|
1766
|
+
...input.seatPurchaseWindowCalendarMonths === void 0 || input.seatPurchaseWindowCalendarMonths === null ? {} : { seatPurchaseWindowCalendarMonths: integer2(input.seatPurchaseWindowCalendarMonths, 1, 120) },
|
|
1762
1767
|
requiredDisclosures: ids(input.requiredDisclosures)
|
|
1763
1768
|
};
|
|
1764
1769
|
}
|
|
@@ -2149,7 +2154,7 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
2149
2154
|
initialTermEndsAt: endsAt,
|
|
2150
2155
|
termStartedAt: paidAt,
|
|
2151
2156
|
termEndsAt: endsAt,
|
|
2152
|
-
seatPurchaseEndsAt:
|
|
2157
|
+
seatPurchaseEndsAt: seatPurchaseDeadline(offering, { initialTermStartedAt: paidAt, termEndsAt: endsAt }),
|
|
2153
2158
|
autoRenew: true,
|
|
2154
2159
|
refundedCents: 0,
|
|
2155
2160
|
revision: 1,
|
|
@@ -3684,9 +3689,37 @@ async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
|
|
|
3684
3689
|
return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
|
|
3685
3690
|
}
|
|
3686
3691
|
|
|
3692
|
+
// src/membership-seat-window.ts
|
|
3693
|
+
async function liftSeatPurchaseWindow(db, scope2, now, limit = 100) {
|
|
3694
|
+
membershipClock(now);
|
|
3695
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200) throw new TypeError("invalid lift limit");
|
|
3696
|
+
const state = await loadMembershipAuthorityState(db, scope2);
|
|
3697
|
+
const rows5 = (await db.query({ networkMembership: { $: { where: { ...scope2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkMembership ?? [];
|
|
3698
|
+
let lifted = 0;
|
|
3699
|
+
for (const member of rows5.slice(0, limit)) {
|
|
3700
|
+
if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) throw new Error("membership lift scope mismatch");
|
|
3701
|
+
const offering = membershipOfferingVersion(member.offeringSnapshot);
|
|
3702
|
+
if (offering.seatPurchaseWindowCalendarMonths === void 0) continue;
|
|
3703
|
+
const { seatPurchaseWindowCalendarMonths: _window, ...windowless } = offering;
|
|
3704
|
+
await db.transact([membershipUpdate("networkMembership", String(member.id), {
|
|
3705
|
+
offeringSnapshot: windowless,
|
|
3706
|
+
seatPurchaseEndsAt: Number(member.termEndsAt),
|
|
3707
|
+
revision: Number(member.revision) + 1,
|
|
3708
|
+
updatedAt: now
|
|
3709
|
+
})], { mutationId: `seat-window-lift:${member.id}:${member.revision}`, guards: [
|
|
3710
|
+
{ ns: "networkMembership", id: String(member.id), exists: true, equals: { revision: member.revision } },
|
|
3711
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } }
|
|
3712
|
+
] });
|
|
3713
|
+
lifted++;
|
|
3714
|
+
}
|
|
3715
|
+
return { lifted, more: rows5.length > limit };
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3687
3718
|
// src/worker-routes-admin-membership-seats.ts
|
|
3688
3719
|
var LIST = "/api/admin/membership-seats";
|
|
3720
|
+
var LIFT = "/api/admin/membership-seats/lift-purchase-window";
|
|
3689
3721
|
var ONE = /^\/api\/admin\/membership-seats\/([A-Za-z0-9][A-Za-z0-9_.:-]{2,159})\/refund$/;
|
|
3722
|
+
var SCOPE = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
3690
3723
|
var PUBLIC = [
|
|
3691
3724
|
"id",
|
|
3692
3725
|
"appId",
|
|
@@ -3711,11 +3744,11 @@ var PUBLIC = [
|
|
|
3711
3744
|
];
|
|
3712
3745
|
var project = (row2) => Object.fromEntries(PUBLIC.filter((key3) => row2[key3] !== void 0).map((key3) => [key3, row2[key3]]));
|
|
3713
3746
|
var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
3714
|
-
const one = url.pathname.match(ONE);
|
|
3715
|
-
if (url.pathname !== LIST && !one) return null;
|
|
3747
|
+
const one = url.pathname.match(ONE), lift = url.pathname === LIFT;
|
|
3748
|
+
if (url.pathname !== LIST && !one && !lift) return null;
|
|
3716
3749
|
const operator = ctx.membershipAuthorityOperator;
|
|
3717
3750
|
if (!operator) return json({ error: "not found" }, 404);
|
|
3718
|
-
if (req.method !== (one ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
|
|
3751
|
+
if (req.method !== (one || lift ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
|
|
3719
3752
|
const raw2 = ctx.makeDb(env), user = await ctx.verifyUser(req, env);
|
|
3720
3753
|
if (!user) return json({ error: "unauthorized" }, 401);
|
|
3721
3754
|
if (!await ctx.isSuperAdminEmail(raw2, user.email, user.userId)) return json({ error: "super-admin required", code: "super_admin_required" }, 403);
|
|
@@ -3725,6 +3758,24 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
|
3725
3758
|
} catch {
|
|
3726
3759
|
return json({ error: "the membership authority is not hosted by this deployment" }, 404);
|
|
3727
3760
|
}
|
|
3761
|
+
if (lift) {
|
|
3762
|
+
let body;
|
|
3763
|
+
try {
|
|
3764
|
+
body = await req.json();
|
|
3765
|
+
} catch {
|
|
3766
|
+
return json({ error: "chapterId and runtime are required" }, 400);
|
|
3767
|
+
}
|
|
3768
|
+
const appId = body.appId ?? body.chapterId;
|
|
3769
|
+
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)) {
|
|
3770
|
+
return json({ error: "chapterId and runtime are required" }, 400);
|
|
3771
|
+
}
|
|
3772
|
+
try {
|
|
3773
|
+
return json({ ok: true, ...await liftSeatPurchaseWindow(db, { appId, chapterId: body.chapterId, environment: env.ODLA_ENV, runtime: body.runtime }, Date.now()) });
|
|
3774
|
+
} catch (error) {
|
|
3775
|
+
const code = error.code;
|
|
3776
|
+
return json({ error: "purchase window lift is pending; retry to confirm", ...code ? { code } : {} }, code ? 409 : 503);
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3728
3779
|
if (!one) {
|
|
3729
3780
|
const rows5 = (await db.query({ networkSeat: { $: { where: { environment: env.ODLA_ENV }, order: { updatedAt: "desc" }, limit: 201 } } })).networkSeat ?? [];
|
|
3730
3781
|
return json({ seats: rows5.slice(0, 200).map(project), truncated: rows5.length > 200 });
|
|
@@ -8772,15 +8823,16 @@ async function readNamedSeatOffer(db, input, now) {
|
|
|
8772
8823
|
const offering = membershipOfferingVersion(member.offeringSnapshot);
|
|
8773
8824
|
const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
|
|
8774
8825
|
if (seat) requireMembershipScope(seat, input);
|
|
8826
|
+
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
8775
8827
|
const offer = {
|
|
8776
8828
|
membershipId: id6,
|
|
8777
8829
|
amountCents: offering.seatPriceCents,
|
|
8778
8830
|
currency: offering.currency,
|
|
8779
8831
|
interval: offering.interval,
|
|
8780
8832
|
termEndsAt: Number(member.termEndsAt),
|
|
8781
|
-
purchaseEndsAt
|
|
8833
|
+
purchaseEndsAt,
|
|
8782
8834
|
autoRenew: member.autoRenew === true,
|
|
8783
|
-
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 <=
|
|
8835
|
+
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))),
|
|
8784
8836
|
...seat ? { seat: {
|
|
8785
8837
|
seatId: String(seat.id),
|
|
8786
8838
|
status: String(seat.status),
|
|
@@ -8788,7 +8840,7 @@ async function readNamedSeatOffer(db, input, now) {
|
|
|
8788
8840
|
recipientEmail: String(seat.recipientEmailNormalized)
|
|
8789
8841
|
} } : {}
|
|
8790
8842
|
};
|
|
8791
|
-
return { ...offer, digest: await namedSeatOfferDigest(offer) };
|
|
8843
|
+
return { ...offer, digest: await namedSeatOfferDigest(offer), purchaseWindowed: offering.seatPurchaseWindowCalendarMonths !== void 0 };
|
|
8792
8844
|
}
|
|
8793
8845
|
|
|
8794
8846
|
// src/named-seat-request.ts
|
|
@@ -13796,7 +13848,11 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
13796
13848
|
membershipUpdate("networkMembership", id6, {
|
|
13797
13849
|
status: status2,
|
|
13798
13850
|
graceEndsAt,
|
|
13799
|
-
...paid ? {
|
|
13851
|
+
...paid ? {
|
|
13852
|
+
termStartedAt: start,
|
|
13853
|
+
termEndsAt: end,
|
|
13854
|
+
...membershipOfferingVersion(member.offeringSnapshot).seatPurchaseWindowCalendarMonths === void 0 ? { seatPurchaseEndsAt: end } : {}
|
|
13855
|
+
} : {},
|
|
13800
13856
|
revision: Number(member.revision) + 1,
|
|
13801
13857
|
updatedAt: now
|
|
13802
13858
|
}),
|
|
@@ -13956,10 +14012,11 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
13956
14012
|
const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
13957
14013
|
let seat = await membershipLoad(db, "networkSeat", id6);
|
|
13958
14014
|
if (!seat) {
|
|
13959
|
-
|
|
14015
|
+
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
|
|
14016
|
+
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
14017
|
+
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) {
|
|
13960
14018
|
membershipFail("named_seat_purchase_unavailable");
|
|
13961
14019
|
}
|
|
13962
|
-
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
|
|
13963
14020
|
const attrs = {
|
|
13964
14021
|
id: id6,
|
|
13965
14022
|
...membershipScope(input),
|
|
@@ -13978,7 +14035,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
13978
14035
|
status: "payment_pending",
|
|
13979
14036
|
preparedAt: now,
|
|
13980
14037
|
termEndsAt: member.termEndsAt,
|
|
13981
|
-
seatPurchaseEndsAt:
|
|
14038
|
+
seatPurchaseEndsAt: purchaseEndsAt,
|
|
13982
14039
|
offeringSnapshot: offering,
|
|
13983
14040
|
revision: 1,
|
|
13984
14041
|
createdAt: now,
|
|
@@ -14180,7 +14237,7 @@ async function confirmNamedMembershipSeatPayment(db, input, now) {
|
|
|
14180
14237
|
}
|
|
14181
14238
|
if (seat.purchasedAt === void 0) {
|
|
14182
14239
|
if (!["payment_processing", "payment_canceling"].includes(String(seat.status))) membershipFail("named_seat_payment_not_expected");
|
|
14183
|
-
const eligible = seat.status !== "payment_canceling" && ["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) && Number(member.refundedCents) === 0 && paidAt <=
|
|
14240
|
+
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);
|
|
14184
14241
|
const status2 = eligible ? "renewal_pending" : "refund_pending", effectId = `${seat.id}:${eligible ? "attach-renewal" : "refund"}`;
|
|
14185
14242
|
try {
|
|
14186
14243
|
await db.transact([
|
|
@@ -15399,7 +15456,7 @@ async function requestNamedSeatReservation(options) {
|
|
|
15399
15456
|
async function requestNamedSeatOffer(options) {
|
|
15400
15457
|
const result = await requestAuthority(options, "seat-offer", { applicationId: options.applicationId, skuId: options.skuId });
|
|
15401
15458
|
const offer = result.seat;
|
|
15402
|
-
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");
|
|
15459
|
+
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");
|
|
15403
15460
|
return offer;
|
|
15404
15461
|
}
|
|
15405
15462
|
async function requestNamedSeatInvitation(options) {
|