@odla-ai/chapter 0.59.0 → 0.59.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 +14 -2
- package/dist/{chunk-WFTWT3V7.js → chunk-P6X3W2XP.js} +11 -5
- package/dist/{chunk-WFTWT3V7.js.map → chunk-P6X3W2XP.js.map} +1 -1
- package/dist/{chunk-Y2H4VURI.js → chunk-SDGVPIXF.js} +3 -2
- package/dist/chunk-SDGVPIXF.js.map +1 -0
- package/dist/index.cjs +161 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +161 -95
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +4 -0
- package/dist/ui/admin/index.js +1 -1
- package/dist/ui/index.js +2 -2
- package/dist/ui/member/index.d.ts +1 -1
- package/dist/ui/member/index.js +1 -1
- package/dist/worker/index.cjs +264 -181
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +122 -14
- package/dist/worker/index.d.ts +122 -14
- package/dist/worker/index.js +264 -181
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-Y2H4VURI.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1997,8 +1997,8 @@ var APPLICATION_LABELS = {
|
|
|
1997
1997
|
state: "State"
|
|
1998
1998
|
};
|
|
1999
1999
|
function humanize(id11) {
|
|
2000
|
-
const
|
|
2001
|
-
return
|
|
2000
|
+
const text8 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
2001
|
+
return text8 ? `${text8[0].toUpperCase()}${text8.slice(1)}` : id11;
|
|
2002
2002
|
}
|
|
2003
2003
|
function applicationDefs(chapter) {
|
|
2004
2004
|
try {
|
|
@@ -4665,6 +4665,9 @@ function namedSeatRecipientState(seat) {
|
|
|
4665
4665
|
return seat.refundReason === "declined" ? "declined" : "refunded";
|
|
4666
4666
|
case "canceled":
|
|
4667
4667
|
return "canceled";
|
|
4668
|
+
// A card-network dispute on the seat's own payment pauses the recipient until it is won or re-charged.
|
|
4669
|
+
case "payment_disputed":
|
|
4670
|
+
return "disputed";
|
|
4668
4671
|
default:
|
|
4669
4672
|
return "payment_pending";
|
|
4670
4673
|
}
|
|
@@ -4860,12 +4863,87 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4860
4863
|
|
|
4861
4864
|
// src/membership-dispute-authority.ts
|
|
4862
4865
|
var import_stripe4 = require("@odla-ai/stripe");
|
|
4866
|
+
|
|
4867
|
+
// src/membership-dispute-transition.ts
|
|
4863
4868
|
var MEMBERSHIP_DISPUTED_STATUS = "payment_disputed";
|
|
4864
4869
|
var DISPUTE_WON_STATUSES = ["won", "warning_closed"];
|
|
4865
4870
|
var CLOSED_STATUSES = [...DISPUTE_WON_STATUSES, "lost"];
|
|
4866
|
-
|
|
4871
|
+
function disputeTransition(row2, fact, eventId, now, terminal) {
|
|
4872
|
+
const status2 = String(fact.status ?? "unknown"), closed = CLOSED_STATUSES.includes(status2);
|
|
4873
|
+
const sameDispute = row2.disputeId === fact.objectId;
|
|
4874
|
+
if (sameDispute && Number(row2.disputeClosedAt ?? 0) > 0 && !closed) return null;
|
|
4875
|
+
const won = DISPUTE_WON_STATUSES.includes(status2);
|
|
4876
|
+
const suspended = row2.status === MEMBERSHIP_DISPUTED_STATUS, done = terminal.includes(String(row2.status));
|
|
4877
|
+
const restored = String(row2.disputePriorStatus ?? (row2.journeyApprovedAt ? "active" : "paid_pending_approval"));
|
|
4878
|
+
const next = done ? String(row2.status) : won ? suspended ? restored : String(row2.status) : MEMBERSHIP_DISPUTED_STATUS;
|
|
4879
|
+
return {
|
|
4880
|
+
status: next,
|
|
4881
|
+
disputeId: fact.objectId,
|
|
4882
|
+
disputeStatus: status2,
|
|
4883
|
+
disputeEventId: eventId,
|
|
4884
|
+
disputeClosedAt: closed ? now : 0,
|
|
4885
|
+
...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
|
|
4886
|
+
...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
|
|
4887
|
+
...sameDispute ? {} : { disputedAt: now },
|
|
4888
|
+
...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(row2.status) } : {},
|
|
4889
|
+
revision: Number(row2.revision) + 1,
|
|
4890
|
+
updatedAt: now
|
|
4891
|
+
};
|
|
4892
|
+
}
|
|
4893
|
+
|
|
4894
|
+
// src/membership-seat-dispute.ts
|
|
4867
4895
|
var text3 = (value) => typeof value === "string" && value ? value : void 0;
|
|
4868
|
-
var
|
|
4896
|
+
var TERMINAL = ["payment_pending", "payment_processing", "payment_canceling", "refund_pending", "refunded", "canceled"];
|
|
4897
|
+
var namedSeatDisputeStanding = (seat, applied) => ({ seatId: String(seat.id), status: String(seat.status), ...text3(seat.disputeStatus) ? { disputeStatus: text3(seat.disputeStatus) } : {}, applied });
|
|
4898
|
+
function disputedChargeCoversSeat(charge, seat) {
|
|
4899
|
+
return charge.seatId === seat.id && charge.customerId === seat.providerCustomerId && charge.paymentIntentId !== void 0 && (charge.paymentIntentId === seat.providerPaymentIntentId || charge.paymentIntentId === seat.rechargePaymentIntentId);
|
|
4900
|
+
}
|
|
4901
|
+
async function reconcileNamedSeatDispute(db, input, now, charge, evidence) {
|
|
4902
|
+
if (!charge.seatId) membershipFail("membership_dispute_scope_mismatch");
|
|
4903
|
+
const membership = async (applied, seat) => {
|
|
4904
|
+
const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4905
|
+
requireMembershipScope(member, input);
|
|
4906
|
+
return {
|
|
4907
|
+
membershipId: id11,
|
|
4908
|
+
status: String(member.status),
|
|
4909
|
+
termEndsAt: Number(member.termEndsAt),
|
|
4910
|
+
graceEndsAt: Number(member.graceEndsAt ?? 0),
|
|
4911
|
+
autoRenew: member.autoRenew === true,
|
|
4912
|
+
applied,
|
|
4913
|
+
...text3(member.disputeStatus) ? { disputeStatus: text3(member.disputeStatus) } : {},
|
|
4914
|
+
...seat ? { seat: namedSeatDisputeStanding(seat, applied) } : {}
|
|
4915
|
+
};
|
|
4916
|
+
};
|
|
4917
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4918
|
+
const state = await loadMembershipAuthorityState(db, input), seat = await membershipLoad(db, "networkSeat", charge.seatId);
|
|
4919
|
+
if (!seat) return membership(false);
|
|
4920
|
+
requireMembershipScope(seat, input);
|
|
4921
|
+
if (charge.customerId !== seat.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
4922
|
+
if (!disputedChargeCoversSeat(charge, seat) || seat.disputeEventId === input.eventId) return membership(seat.disputeEventId === input.eventId, seat);
|
|
4923
|
+
const attrs = disputeTransition(seat, evidence.fact, input.eventId, now, TERMINAL);
|
|
4924
|
+
if (!attrs) return membership(false, seat);
|
|
4925
|
+
try {
|
|
4926
|
+
await db.transact([membershipUpdate("networkSeat", String(seat.id), attrs)], { mutationId: `seat-dispute:${seat.id}:${input.eventId}`, guards: [
|
|
4927
|
+
{ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision } },
|
|
4928
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
4929
|
+
{ ns: "networkCommercialEvents", id: String(evidence.row.id), exists: true, equals: { digest: evidence.fact.digest } },
|
|
4930
|
+
evidence.guard
|
|
4931
|
+
] });
|
|
4932
|
+
} catch (error) {
|
|
4933
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
4934
|
+
continue;
|
|
4935
|
+
}
|
|
4936
|
+
const saved = await membershipLoad(db, "networkSeat", String(seat.id));
|
|
4937
|
+
requireMembershipScope(saved, input);
|
|
4938
|
+
return membership(true, saved);
|
|
4939
|
+
}
|
|
4940
|
+
return membershipFail("membership_dispute_retry_required");
|
|
4941
|
+
}
|
|
4942
|
+
|
|
4943
|
+
// src/membership-dispute-authority.ts
|
|
4944
|
+
var TERMINAL2 = ["refunded", "refund_pending", "canceled", "declined", "restarting", "superseded"];
|
|
4945
|
+
var text4 = (value) => typeof value === "string" && value ? value : void 0;
|
|
4946
|
+
var refOf = (value) => typeof value === "string" ? text4(value) : text4(value?.id);
|
|
4869
4947
|
async function readDisputedCharge(secretKey, chargeId, fetch2) {
|
|
4870
4948
|
if (!/^ch_[A-Za-z0-9]+$/.test(chargeId)) membershipFail("invalid_dispute_charge");
|
|
4871
4949
|
const config = { secretKey, ...fetch2 ? { fetch: fetch2 } : {} };
|
|
@@ -4879,7 +4957,7 @@ async function readDisputedCharge(secretKey, chargeId, fetch2) {
|
|
|
4879
4957
|
if (!invoice.ok || invoice.body.id !== invoiceId) membershipFail("membership_dispute_provider_unavailable");
|
|
4880
4958
|
subscriptionId = refOf(invoice.body.subscription) ?? refOf(invoice.body.parent?.subscription_details?.subscription);
|
|
4881
4959
|
}
|
|
4882
|
-
const seatId =
|
|
4960
|
+
const seatId = text4(charge.body.metadata?.seatId);
|
|
4883
4961
|
return {
|
|
4884
4962
|
chargeId,
|
|
4885
4963
|
customerId,
|
|
@@ -4905,37 +4983,23 @@ async function reconcileMembershipDispute(db, input, now, secretKey, resolved) {
|
|
|
4905
4983
|
termEndsAt: Number(value.termEndsAt),
|
|
4906
4984
|
graceEndsAt: Number(value.graceEndsAt ?? 0),
|
|
4907
4985
|
autoRenew: value.autoRenew === true,
|
|
4908
|
-
...
|
|
4986
|
+
...text4(value.disputeStatus) ? { disputeStatus: text4(value.disputeStatus) } : {},
|
|
4909
4987
|
applied
|
|
4910
4988
|
});
|
|
4911
4989
|
const known = await membershipLoad(db, "networkMembership", id11);
|
|
4912
4990
|
requireMembershipScope(known, input);
|
|
4913
4991
|
if (known.disputeEventId === input.eventId) return receipt(known);
|
|
4992
|
+
const seatKnown = known.namedSeatId ? await membershipLoad(db, "networkSeat", String(known.namedSeatId)) : void 0;
|
|
4993
|
+
if (seatKnown?.disputeEventId === input.eventId) return { ...receipt(known, true), seat: namedSeatDisputeStanding(seatKnown, true) };
|
|
4914
4994
|
const charge = resolved && resolved.chargeId === fact.chargeId ? resolved : await readDisputedCharge(secretKey, fact.chargeId);
|
|
4995
|
+
if (charge.customerId !== known.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
4996
|
+
if (charge.seatId) return reconcileNamedSeatDispute(db, input, now, charge, { row: row2, fact, guard: evidenceGuard });
|
|
4915
4997
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4916
4998
|
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4917
4999
|
requireMembershipScope(member, input);
|
|
4918
|
-
if (charge.customerId !== member.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
4919
5000
|
if (!disputedChargeCoversMembership(charge, member) || member.disputeEventId === input.eventId) return receipt(member);
|
|
4920
|
-
const
|
|
4921
|
-
|
|
4922
|
-
if (sameDispute && Number(member.disputeClosedAt ?? 0) > 0 && !closed) return receipt(member, false);
|
|
4923
|
-
const won = DISPUTE_WON_STATUSES.includes(status2);
|
|
4924
|
-
const suspended = member.status === MEMBERSHIP_DISPUTED_STATUS, terminal = TERMINAL.includes(String(member.status));
|
|
4925
|
-
const next = terminal ? String(member.status) : won ? suspended ? String(member.disputePriorStatus ?? (member.journeyApprovedAt ? "active" : "paid_pending_approval")) : String(member.status) : MEMBERSHIP_DISPUTED_STATUS;
|
|
4926
|
-
const attrs = {
|
|
4927
|
-
status: next,
|
|
4928
|
-
disputeId: fact.objectId,
|
|
4929
|
-
disputeStatus: status2,
|
|
4930
|
-
disputeEventId: input.eventId,
|
|
4931
|
-
disputeClosedAt: closed ? now : 0,
|
|
4932
|
-
...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
|
|
4933
|
-
...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
|
|
4934
|
-
...sameDispute ? {} : { disputedAt: now },
|
|
4935
|
-
...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(member.status) } : {},
|
|
4936
|
-
revision: Number(member.revision) + 1,
|
|
4937
|
-
updatedAt: now
|
|
4938
|
-
};
|
|
5001
|
+
const attrs = disputeTransition(member, fact, input.eventId, now, TERMINAL2);
|
|
5002
|
+
if (!attrs) return receipt(member, false);
|
|
4939
5003
|
try {
|
|
4940
5004
|
await db.transact([membershipUpdate("networkMembership", id11, attrs)], { mutationId: `membership-dispute:${id11}:${input.eventId}`, guards: [
|
|
4941
5005
|
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
|
|
@@ -6339,7 +6403,7 @@ async function readBoundedJson(response, signal, maxBytes, label) {
|
|
|
6339
6403
|
};
|
|
6340
6404
|
signal.addEventListener("abort", cancel, { once: true });
|
|
6341
6405
|
let size = 0;
|
|
6342
|
-
let
|
|
6406
|
+
let text8 = "";
|
|
6343
6407
|
const decoder = new TextDecoder();
|
|
6344
6408
|
try {
|
|
6345
6409
|
while (true) {
|
|
@@ -6349,10 +6413,10 @@ async function readBoundedJson(response, signal, maxBytes, label) {
|
|
|
6349
6413
|
if (done) break;
|
|
6350
6414
|
size += value.byteLength;
|
|
6351
6415
|
if (size > maxBytes) throw new TypeError(`${label} exceeds limit`);
|
|
6352
|
-
|
|
6416
|
+
text8 += decoder.decode(value, { stream: true });
|
|
6353
6417
|
}
|
|
6354
6418
|
try {
|
|
6355
|
-
return JSON.parse(
|
|
6419
|
+
return JSON.parse(text8 + decoder.decode());
|
|
6356
6420
|
} catch {
|
|
6357
6421
|
throw new TypeError(`invalid ${label}`);
|
|
6358
6422
|
}
|
|
@@ -6839,6 +6903,19 @@ var id10 = () => attr9("string", false, true, true);
|
|
|
6839
6903
|
var key2 = (optional = false) => attr9("string", optional, false, true);
|
|
6840
6904
|
var scope = () => ({ appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), applicationId: key2(), skuId: key2() });
|
|
6841
6905
|
var times = () => ({ revision: attr9("number"), createdAt: attr9("number"), updatedAt: attr9("number") });
|
|
6906
|
+
var dispute = () => ({
|
|
6907
|
+
disputeId: key2(true),
|
|
6908
|
+
disputeStatus: attr9("string", true),
|
|
6909
|
+
disputeReason: attr9("string", true),
|
|
6910
|
+
disputeAmountCents: attr9("number", true),
|
|
6911
|
+
disputedAt: attr9("number", true),
|
|
6912
|
+
disputeClosedAt: attr9("number", true),
|
|
6913
|
+
disputePriorStatus: attr9("string", true),
|
|
6914
|
+
disputeEventId: key2(true),
|
|
6915
|
+
rechargePaymentIntentId: key2(true),
|
|
6916
|
+
rechargedAt: attr9("number", true),
|
|
6917
|
+
rechargeAttempt: attr9("json", true)
|
|
6918
|
+
});
|
|
6842
6919
|
var membershipAuthorityEntities = {
|
|
6843
6920
|
networkMembership: { attrs: {
|
|
6844
6921
|
id: id10(),
|
|
@@ -6879,19 +6956,7 @@ var membershipAuthorityEntities = {
|
|
|
6879
6956
|
...times(),
|
|
6880
6957
|
refundRequestedAt: attr9("number", true),
|
|
6881
6958
|
restartId: key2(true),
|
|
6882
|
-
|
|
6883
|
-
// the status the membership held before it was suspended, and the re-issued charge once an operator collected again.
|
|
6884
|
-
disputeId: key2(true),
|
|
6885
|
-
disputeStatus: attr9("string", true),
|
|
6886
|
-
disputeReason: attr9("string", true),
|
|
6887
|
-
disputeAmountCents: attr9("number", true),
|
|
6888
|
-
disputedAt: attr9("number", true),
|
|
6889
|
-
disputeClosedAt: attr9("number", true),
|
|
6890
|
-
disputePriorStatus: attr9("string", true),
|
|
6891
|
-
disputeEventId: key2(true),
|
|
6892
|
-
rechargePaymentIntentId: key2(true),
|
|
6893
|
-
rechargedAt: attr9("number", true),
|
|
6894
|
-
rechargeAttempt: attr9("json", true)
|
|
6959
|
+
...dispute()
|
|
6895
6960
|
} },
|
|
6896
6961
|
membershipRestart: { attrs: {
|
|
6897
6962
|
id: id10(),
|
|
@@ -6967,7 +7032,8 @@ var membershipAuthorityEntities = {
|
|
|
6967
7032
|
refundedCents: attr9("number", true),
|
|
6968
7033
|
providerRefundIds: attr9("json", true),
|
|
6969
7034
|
regiftedAt: attr9("number", true),
|
|
6970
|
-
formerRecipients: attr9("json", true)
|
|
7035
|
+
formerRecipients: attr9("json", true),
|
|
7036
|
+
...dispute()
|
|
6971
7037
|
} },
|
|
6972
7038
|
networkSeatRecipient: { attrs: { id: id10(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
|
|
6973
7039
|
membershipEffect: { attrs: {
|
|
@@ -7327,7 +7393,7 @@ var record2 = (value) => value !== null && typeof value === "object" && !Array.i
|
|
|
7327
7393
|
function unknownKeys2(value, allowed2, path, errors) {
|
|
7328
7394
|
for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
7329
7395
|
}
|
|
7330
|
-
function
|
|
7396
|
+
function text5(value, path, errors, max = MAX_TEXT2, required = true) {
|
|
7331
7397
|
if (value === void 0 && !required) return void 0;
|
|
7332
7398
|
if (typeof value !== "string" || required && !value.trim()) {
|
|
7333
7399
|
errors.push(`${path} must be a non-empty string`);
|
|
@@ -7353,7 +7419,7 @@ function scopeValue(value, path, errors) {
|
|
|
7353
7419
|
return { kind: raw2.kind };
|
|
7354
7420
|
}
|
|
7355
7421
|
if (raw2.kind === "chapter") {
|
|
7356
|
-
const chapterId =
|
|
7422
|
+
const chapterId = text5(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
|
|
7357
7423
|
if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
|
|
7358
7424
|
return { kind: "chapter", chapterId };
|
|
7359
7425
|
}
|
|
@@ -7367,22 +7433,22 @@ function resourceValue(value, path, errors) {
|
|
|
7367
7433
|
return null;
|
|
7368
7434
|
}
|
|
7369
7435
|
unknownKeys2(raw2, RESOURCE_KEYS, path, errors);
|
|
7370
|
-
const id11 =
|
|
7436
|
+
const id11 = text5(raw2.id, `${path}.id`, errors, 80) ?? "";
|
|
7371
7437
|
if (id11 && !ID2.test(id11)) errors.push(`${path}.id must be lowercase kebab-case`);
|
|
7372
|
-
const kind =
|
|
7438
|
+
const kind = text5(raw2.kind, `${path}.kind`, errors) ?? "";
|
|
7373
7439
|
if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
|
|
7374
|
-
const title =
|
|
7375
|
-
const summary =
|
|
7376
|
-
const status2 =
|
|
7440
|
+
const title = text5(raw2.title, `${path}.title`, errors, 240) ?? "";
|
|
7441
|
+
const summary = text5(raw2.summary, `${path}.summary`, errors, 1e3, false);
|
|
7442
|
+
const status2 = text5(raw2.status, `${path}.status`, errors) ?? "";
|
|
7377
7443
|
if (!STATUSES.has(status2)) errors.push(`${path}.status must be draft, published, or archived`);
|
|
7378
7444
|
const revision = integer3(raw2.revision, `${path}.revision`, errors);
|
|
7379
7445
|
const scope2 = scopeValue(raw2.scope, `${path}.scope`, errors) ?? { kind: "global" };
|
|
7380
|
-
const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) =>
|
|
7446
|
+
const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text5(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
|
|
7381
7447
|
if (!Array.isArray(raw2.tags)) errors.push(`${path}.tags must be an array`);
|
|
7382
7448
|
if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
|
|
7383
7449
|
if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
|
|
7384
|
-
const bodyMarkdown =
|
|
7385
|
-
const url =
|
|
7450
|
+
const bodyMarkdown = text5(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
|
|
7451
|
+
const url = text5(raw2.url, `${path}.url`, errors, 2048, false);
|
|
7386
7452
|
if (url) {
|
|
7387
7453
|
try {
|
|
7388
7454
|
if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
|
|
@@ -7394,9 +7460,9 @@ function resourceValue(value, path, errors) {
|
|
|
7394
7460
|
if (raw2.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
|
|
7395
7461
|
if (asset) unknownKeys2(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
|
|
7396
7462
|
const brandAsset = asset ? {
|
|
7397
|
-
bookId:
|
|
7398
|
-
assetId:
|
|
7399
|
-
...asset.filename === void 0 ? {} : { filename:
|
|
7463
|
+
bookId: text5(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
|
|
7464
|
+
assetId: text5(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
|
|
7465
|
+
...asset.filename === void 0 ? {} : { filename: text5(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
|
|
7400
7466
|
} : void 0;
|
|
7401
7467
|
if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
|
|
7402
7468
|
if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
|
|
@@ -7411,17 +7477,17 @@ function resourceValue(value, path, errors) {
|
|
|
7411
7477
|
if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
|
|
7412
7478
|
if (promotedRaw) unknownKeys2(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
|
|
7413
7479
|
const promotedFrom = promotedRaw ? {
|
|
7414
|
-
chapterId:
|
|
7415
|
-
resourceId:
|
|
7480
|
+
chapterId: text5(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
|
|
7481
|
+
resourceId: text5(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
|
|
7416
7482
|
resourceRevision: integer3(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
|
|
7417
7483
|
documentRevision: integer3(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
|
|
7418
|
-
promotedBy:
|
|
7484
|
+
promotedBy: text5(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
|
|
7419
7485
|
promotedAt: integer3(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
|
|
7420
7486
|
} : void 0;
|
|
7421
7487
|
const provenance = {
|
|
7422
|
-
createdBy:
|
|
7488
|
+
createdBy: text5(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
|
|
7423
7489
|
createdAt: integer3(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
|
|
7424
|
-
updatedBy:
|
|
7490
|
+
updatedBy: text5(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
|
|
7425
7491
|
updatedAt: integer3(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
|
|
7426
7492
|
...promotedFrom ? { promotedFrom } : {}
|
|
7427
7493
|
};
|
|
@@ -7440,7 +7506,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
7440
7506
|
if (sourceRaw) unknownKeys2(sourceRaw, SOURCE_KEYS2, "document.source", errors);
|
|
7441
7507
|
const sourceType = sourceRaw?.type;
|
|
7442
7508
|
if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
|
|
7443
|
-
const sourceAppId =
|
|
7509
|
+
const sourceAppId = text5(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
|
|
7444
7510
|
if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
7445
7511
|
if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
|
|
7446
7512
|
if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
|
|
@@ -7448,8 +7514,8 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
7448
7514
|
if (raw2.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
|
|
7449
7515
|
if (targetRaw) unknownKeys2(targetRaw, TARGET_KEYS2, "document.target", errors);
|
|
7450
7516
|
const target = targetRaw ? {
|
|
7451
|
-
appId:
|
|
7452
|
-
environment:
|
|
7517
|
+
appId: text5(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
|
|
7518
|
+
environment: text5(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
|
|
7453
7519
|
} : void 0;
|
|
7454
7520
|
if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
|
|
7455
7521
|
if (!Array.isArray(raw2.resources)) errors.push("document.resources must be an array");
|
|
@@ -7544,9 +7610,9 @@ async function createBuilderLibraryEnvelope(document) {
|
|
|
7544
7610
|
}
|
|
7545
7611
|
|
|
7546
7612
|
// src/email-retirement.ts
|
|
7547
|
-
function isRetiredEmailCopy(subject,
|
|
7613
|
+
function isRetiredEmailCopy(subject, text8) {
|
|
7548
7614
|
const subjectCopy = subject.trimStart().replace(/^\[dev\]\s*/i, "");
|
|
7549
|
-
const bodyCopy =
|
|
7615
|
+
const bodyCopy = text8.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
|
|
7550
7616
|
return /^\(\s*unused\s*:/i.test(subjectCopy) || /^Unused\.\s*Switched off\b/i.test(bodyCopy);
|
|
7551
7617
|
}
|
|
7552
7618
|
|
|
@@ -7624,10 +7690,10 @@ function planDelivery(input) {
|
|
|
7624
7690
|
if (isRetiredEmailCopy(renderedSubject, renderedText)) return { deliver: false, reason: "template-retired" };
|
|
7625
7691
|
if ((0, import_crm5.validateEmailCopy)({ subject: renderedSubject, text: renderedText }, { allowedVariables: [] }).length) return { deliver: false, reason: "template-invalid" };
|
|
7626
7692
|
const subject = (redirect ? "[dev] " : "") + renderedSubject;
|
|
7627
|
-
const
|
|
7693
|
+
const text8 = redirect ? `(dev redirect; original recipient: ${input.to})
|
|
7628
7694
|
|
|
7629
7695
|
` + renderedText : renderedText;
|
|
7630
|
-
return { deliver: true, transport, to, subject, text:
|
|
7696
|
+
return { deliver: true, transport, to, subject, text: text8, redirected: redirect };
|
|
7631
7697
|
}
|
|
7632
7698
|
|
|
7633
7699
|
// src/email-readiness.ts
|
|
@@ -8283,7 +8349,7 @@ var ID4 = /^[a-z][a-z0-9-]{1,79}$/;
|
|
|
8283
8349
|
var PRICE = /^price_[A-Za-z0-9]{12,}$/;
|
|
8284
8350
|
var CURRENCY = /^[a-z]{3}$/;
|
|
8285
8351
|
var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
8286
|
-
var
|
|
8352
|
+
var text6 = (value, path, errors, max) => {
|
|
8287
8353
|
if (typeof value !== "string" || !value.trim()) {
|
|
8288
8354
|
errors.push(`${path} is required`);
|
|
8289
8355
|
return "";
|
|
@@ -8311,23 +8377,23 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
8311
8377
|
}
|
|
8312
8378
|
const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
|
|
8313
8379
|
for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
8314
|
-
const id11 =
|
|
8380
|
+
const id11 = text6(item.id, `${path}.id`, errors, 80);
|
|
8315
8381
|
if (id11 && !ID4.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
8316
8382
|
if (ids2.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
8317
8383
|
ids2.add(id11);
|
|
8318
8384
|
const targetRaw = record3(item.target);
|
|
8319
8385
|
let target = { type: "entitlement", entitlementKey: "invalid" };
|
|
8320
8386
|
if (targetRaw?.type === "tier") {
|
|
8321
|
-
const tierId =
|
|
8387
|
+
const tierId = text6(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
|
|
8322
8388
|
if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
|
|
8323
8389
|
target = { type: "tier", tierId };
|
|
8324
8390
|
} else if (targetRaw?.type === "entitlement") {
|
|
8325
|
-
const entitlementKey =
|
|
8391
|
+
const entitlementKey = text6(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
|
|
8326
8392
|
if (entitlementKey && !ID4.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
|
|
8327
8393
|
target = { type: "entitlement", entitlementKey };
|
|
8328
8394
|
} else errors.push(`${path}.target.type must be tier or entitlement`);
|
|
8329
8395
|
if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
|
|
8330
|
-
const stripePriceId =
|
|
8396
|
+
const stripePriceId = text6(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
|
|
8331
8397
|
if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
|
|
8332
8398
|
if (!Number.isSafeInteger(item.priceCents) || Number(item.priceCents) < 1 || Number(item.priceCents) > 1e8) errors.push(`${path}.priceCents must be an integer from 1 to 100,000,000`);
|
|
8333
8399
|
const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
|
|
@@ -8349,9 +8415,9 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
8349
8415
|
redemptionLifetimeHours: lifetime,
|
|
8350
8416
|
...activeFrom !== void 0 ? { activeFrom } : {},
|
|
8351
8417
|
...activeUntil !== void 0 ? { activeUntil } : {},
|
|
8352
|
-
title:
|
|
8353
|
-
description:
|
|
8354
|
-
ctaLabel:
|
|
8418
|
+
title: text6(item.title, `${path}.title`, errors, 160),
|
|
8419
|
+
description: text6(item.description, `${path}.description`, errors, 2e3),
|
|
8420
|
+
ctaLabel: text6(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
|
|
8355
8421
|
});
|
|
8356
8422
|
}
|
|
8357
8423
|
return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
|
|
@@ -9434,10 +9500,10 @@ function boundedString(value, path, errors, max, required = false) {
|
|
|
9434
9500
|
errors.push(`${path} must be a string`);
|
|
9435
9501
|
return void 0;
|
|
9436
9502
|
}
|
|
9437
|
-
const
|
|
9438
|
-
if (required && !
|
|
9439
|
-
if (
|
|
9440
|
-
return
|
|
9503
|
+
const text8 = value.trim();
|
|
9504
|
+
if (required && !text8) errors.push(`${path} is required`);
|
|
9505
|
+
if (text8.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
|
|
9506
|
+
return text8;
|
|
9441
9507
|
}
|
|
9442
9508
|
function validateMembershipPageDocument(input, options = {}) {
|
|
9443
9509
|
const errors = [];
|
|
@@ -9486,8 +9552,8 @@ function validateMembershipPageDocument(input, options = {}) {
|
|
|
9486
9552
|
else {
|
|
9487
9553
|
if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
|
|
9488
9554
|
benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
|
|
9489
|
-
const
|
|
9490
|
-
return
|
|
9555
|
+
const text8 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
|
|
9556
|
+
return text8 ? [text8] : [];
|
|
9491
9557
|
});
|
|
9492
9558
|
}
|
|
9493
9559
|
}
|
|
@@ -9586,7 +9652,7 @@ var recordOf = (value) => value !== null && typeof value === "object" && !Array.
|
|
|
9586
9652
|
function unknownKeys3(value, allowed2, path, errors) {
|
|
9587
9653
|
for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
9588
9654
|
}
|
|
9589
|
-
function
|
|
9655
|
+
function text7(value, path, errors, max = 65536) {
|
|
9590
9656
|
if (typeof value !== "string" || !value.trim()) {
|
|
9591
9657
|
errors.push(`${path} is required`);
|
|
9592
9658
|
return "";
|
|
@@ -9609,11 +9675,11 @@ function tiersOf(value, errors) {
|
|
|
9609
9675
|
return [];
|
|
9610
9676
|
}
|
|
9611
9677
|
unknownKeys3(item, TIER_KEYS2, path, errors);
|
|
9612
|
-
const id11 =
|
|
9678
|
+
const id11 = text7(item.id, `${path}.id`, errors, 80);
|
|
9613
9679
|
if (id11 && !TIER.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
9614
9680
|
if (seen.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
9615
9681
|
seen.add(id11);
|
|
9616
|
-
const name =
|
|
9682
|
+
const name = text7(item.name, `${path}.name`, errors, 160);
|
|
9617
9683
|
if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
|
|
9618
9684
|
if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
|
|
9619
9685
|
if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
|
|
@@ -9631,7 +9697,7 @@ function tiersOf(value, errors) {
|
|
|
9631
9697
|
if (item.interval !== void 0 && item.interval !== "year" && item.interval !== "month") {
|
|
9632
9698
|
errors.push(`${path}.interval must be year or month`);
|
|
9633
9699
|
}
|
|
9634
|
-
const blurb = item.blurb === void 0 ? void 0 :
|
|
9700
|
+
const blurb = item.blurb === void 0 ? void 0 : text7(item.blurb, `${path}.blurb`, errors, 4e3);
|
|
9635
9701
|
return [{
|
|
9636
9702
|
id: id11,
|
|
9637
9703
|
name,
|
|
@@ -9689,7 +9755,7 @@ function policyOf(value, errors, tiers2) {
|
|
|
9689
9755
|
const result = {};
|
|
9690
9756
|
for (const key3 of allowed2) {
|
|
9691
9757
|
if (input[key3] === void 0 && key3 !== "disclaimerText" && key3 !== "refundPolicyText") continue;
|
|
9692
|
-
result[key3] =
|
|
9758
|
+
result[key3] = text7(input[key3], `document.policy.${key3}`, errors);
|
|
9693
9759
|
}
|
|
9694
9760
|
const tierPolicies = {};
|
|
9695
9761
|
if (input.tierPolicies !== void 0) {
|
|
@@ -9704,8 +9770,8 @@ function policyOf(value, errors, tiers2) {
|
|
|
9704
9770
|
}
|
|
9705
9771
|
unknownKeys3(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
|
|
9706
9772
|
tierPolicies[id11] = {
|
|
9707
|
-
refundPolicyText:
|
|
9708
|
-
...policy2.trustCopy !== void 0 ? { trustCopy:
|
|
9773
|
+
refundPolicyText: text7(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
|
|
9774
|
+
...policy2.trustCopy !== void 0 ? { trustCopy: text7(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
|
|
9709
9775
|
};
|
|
9710
9776
|
}
|
|
9711
9777
|
}
|
|
@@ -9745,9 +9811,9 @@ function validateSignupControlDocument(input) {
|
|
|
9745
9811
|
if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
|
|
9746
9812
|
const source = recordOf(root.source);
|
|
9747
9813
|
const target = recordOf(root.target);
|
|
9748
|
-
const appId =
|
|
9749
|
-
const chapterId =
|
|
9750
|
-
const environment =
|
|
9814
|
+
const appId = text7(source?.appId, "document.source.appId", errors, 63);
|
|
9815
|
+
const chapterId = text7(target?.chapterId, "document.target.chapterId", errors, 63);
|
|
9816
|
+
const environment = text7(target?.environment, "document.target.environment", errors, 63);
|
|
9751
9817
|
if (appId && !SLUG9.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
9752
9818
|
if (chapterId && !SLUG9.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
|
|
9753
9819
|
if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
|
|
@@ -9924,9 +9990,9 @@ function markdownBenefits(lines, path, errors) {
|
|
|
9924
9990
|
errors.push(`${path} line ${index2 + 1} must be a Markdown bullet`);
|
|
9925
9991
|
continue;
|
|
9926
9992
|
}
|
|
9927
|
-
const
|
|
9928
|
-
if (
|
|
9929
|
-
if (
|
|
9993
|
+
const text8 = (match[1] ?? "").trim();
|
|
9994
|
+
if (text8.length > 2e3) errors.push(`${path} line ${index2 + 1} must be at most 2,000 characters`);
|
|
9995
|
+
if (text8) benefits.push(text8);
|
|
9930
9996
|
}
|
|
9931
9997
|
if (benefits.length > MAX_BENEFITS2) errors.push(`${path} supports at most ${MAX_BENEFITS2} entries`);
|
|
9932
9998
|
return benefits.slice(0, MAX_BENEFITS2);
|