@odla-ai/chapter 0.33.2 → 0.35.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 +83 -2
- package/dist/index.cjs +217 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +80 -6
- package/dist/index.d.ts +80 -6
- package/dist/index.js +219 -3
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +160 -10
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +99 -1
- package/dist/worker/index.d.ts +99 -1
- package/dist/worker/index.js +160 -10
- package/dist/worker/index.js.map +1 -1
- package/package.json +3 -1
package/dist/worker/index.cjs
CHANGED
|
@@ -22,7 +22,9 @@ var worker_exports = {};
|
|
|
22
22
|
__export(worker_exports, {
|
|
23
23
|
chapterWorker: () => chapterWorker,
|
|
24
24
|
createWorkerContext: () => createWorkerContext,
|
|
25
|
-
deliverSignupControl: () => deliverSignupControl
|
|
25
|
+
deliverSignupControl: () => deliverSignupControl,
|
|
26
|
+
renderActiveSignupControlHtml: () => renderActiveSignupControlHtml,
|
|
27
|
+
withActiveSignupControlTheme: () => withActiveSignupControlTheme
|
|
26
28
|
});
|
|
27
29
|
module.exports = __toCommonJS(worker_exports);
|
|
28
30
|
|
|
@@ -570,7 +572,7 @@ function groupAttrs(document) {
|
|
|
570
572
|
return {
|
|
571
573
|
...first ? { standardPriceCents: first.priceCents } : {},
|
|
572
574
|
foundingDiscountCents: 0,
|
|
573
|
-
stripePriceId: firstPaid
|
|
575
|
+
...firstPaid?.stripePriceId ? { stripePriceId: firstPaid.stripePriceId } : {},
|
|
574
576
|
...document.policy ?? {},
|
|
575
577
|
...document.scheduling ? { schedulingJson: document.scheduling } : {}
|
|
576
578
|
};
|
|
@@ -596,24 +598,35 @@ async function materializeSignupControl(db, envelope, previous, receivedAt) {
|
|
|
596
598
|
}
|
|
597
599
|
}
|
|
598
600
|
for (const tier of document.tiers) {
|
|
599
|
-
|
|
601
|
+
const tierRef = byPublicId("tiers", tier.id);
|
|
602
|
+
ops.push({ t: "update", ns: "tiers", id: tierRef, attrs: {
|
|
600
603
|
id: tier.id,
|
|
601
604
|
groupId: document.target.chapterId,
|
|
602
605
|
name: tier.name,
|
|
603
606
|
priceCents: tier.priceCents,
|
|
604
|
-
stripePriceId: tier.stripePriceId
|
|
605
|
-
blurb: tier.blurb
|
|
607
|
+
...tier.stripePriceId ? { stripePriceId: tier.stripePriceId } : {},
|
|
608
|
+
...tier.blurb ? { blurb: tier.blurb } : {},
|
|
606
609
|
sortOrder: tier.sortOrder,
|
|
607
610
|
active: tier.available,
|
|
608
611
|
createdAt: receivedAt
|
|
609
612
|
} });
|
|
613
|
+
const retract = [
|
|
614
|
+
...!tier.stripePriceId ? ["stripePriceId"] : [],
|
|
615
|
+
...!tier.blurb ? ["blurb"] : []
|
|
616
|
+
];
|
|
617
|
+
if (retract.length) ops.push({ t: "retract", ns: "tiers", id: tierRef, attrs: retract });
|
|
610
618
|
}
|
|
619
|
+
const groupRef = byPublicId("groups", document.target.chapterId);
|
|
620
|
+
const attrs = groupAttrs(document);
|
|
611
621
|
ops.push({
|
|
612
622
|
t: "update",
|
|
613
623
|
ns: "groups",
|
|
614
|
-
id:
|
|
615
|
-
attrs
|
|
624
|
+
id: groupRef,
|
|
625
|
+
attrs
|
|
616
626
|
});
|
|
627
|
+
if (!("stripePriceId" in attrs)) {
|
|
628
|
+
ops.push({ t: "retract", ns: "groups", id: groupRef, attrs: ["stripePriceId"] });
|
|
629
|
+
}
|
|
617
630
|
const id = `signup-control:${document.target.environment}:${document.revision}`;
|
|
618
631
|
ops.push({ t: "update", ns: "signupControls", id, attrs: {
|
|
619
632
|
id,
|
|
@@ -1739,12 +1752,18 @@ function themeOf(value, errors) {
|
|
|
1739
1752
|
}
|
|
1740
1753
|
unknownKeys(input, /* @__PURE__ */ new Set(["tokens"]), "document.theme", errors);
|
|
1741
1754
|
if (Object.keys(tokens).length > 256) errors.push("document.theme.tokens supports at most 256 entries");
|
|
1755
|
+
let serializedLength = 0;
|
|
1742
1756
|
for (const [key, token] of Object.entries(tokens)) {
|
|
1743
|
-
if (!/^--[a-z0-9-]{
|
|
1744
|
-
|
|
1745
|
-
|
|
1757
|
+
if (!/^--ui-[a-z0-9][a-z0-9-]{0,94}$/.test(key)) {
|
|
1758
|
+
errors.push(`document.theme.tokens.${key} must be an allowlisted --ui-* custom property`);
|
|
1759
|
+
}
|
|
1760
|
+
const rendered = String(token).trim();
|
|
1761
|
+
serializedLength += key.length + rendered.length + 4;
|
|
1762
|
+
if (typeof token !== "string" && typeof token !== "number" || typeof token === "number" && !Number.isFinite(token) || !rendered || rendered.length > 1e3 || /[\u0000-\u001f\u007f\\<>{};]|\/\*|\*\/|(?:^|[^a-z])(?:url|expression)\s*\(|@import/i.test(rendered)) {
|
|
1763
|
+
errors.push(`document.theme.tokens.${key} must be a bounded breakout-safe CSS value`);
|
|
1746
1764
|
}
|
|
1747
1765
|
}
|
|
1766
|
+
if (serializedLength > 65536) errors.push("document.theme.tokens exceeds the serialized size bound");
|
|
1748
1767
|
return { tokens };
|
|
1749
1768
|
}
|
|
1750
1769
|
function validateSignupControlDocument(input) {
|
|
@@ -4066,6 +4085,137 @@ async function deliverSignupControl(options) {
|
|
|
4066
4085
|
return result;
|
|
4067
4086
|
}
|
|
4068
4087
|
|
|
4088
|
+
// src/signup-control-theme.ts
|
|
4089
|
+
var SIGNUP_CONTROL_THEME_MARKER = "<!--odla:signup-control-theme-->";
|
|
4090
|
+
var SIGNUP_CONTROL_THEME_STYLE_ID = "odla-signup-control-theme";
|
|
4091
|
+
var TOKEN_NAME = /^--ui-[a-z0-9][a-z0-9-]{0,94}$/;
|
|
4092
|
+
var DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
4093
|
+
var UNSAFE_VALUE = /[\u0000-\u001f\u007f\\<>{};]|\/\*|\*\/|(?:^|[^a-z])(?:url|expression)\s*\(|@import/i;
|
|
4094
|
+
var MAX_TOKENS = 256;
|
|
4095
|
+
var MAX_VALUE_LENGTH = 1e3;
|
|
4096
|
+
var MAX_SERIALIZED_LENGTH = 65536;
|
|
4097
|
+
function validatedTokens(input) {
|
|
4098
|
+
const entries = Object.entries(input ?? {});
|
|
4099
|
+
if (entries.length > MAX_TOKENS) {
|
|
4100
|
+
throw new TypeError(`signup-control theme supports at most ${MAX_TOKENS} tokens`);
|
|
4101
|
+
}
|
|
4102
|
+
const tokens = {};
|
|
4103
|
+
let serializedLength = 0;
|
|
4104
|
+
for (const [name, raw] of entries.sort(([left], [right]) => left.localeCompare(right))) {
|
|
4105
|
+
if (!TOKEN_NAME.test(name)) {
|
|
4106
|
+
throw new TypeError(`signup-control theme token "${name}" must be an allowlisted --ui-* property`);
|
|
4107
|
+
}
|
|
4108
|
+
if (typeof raw !== "string" && typeof raw !== "number" || typeof raw === "number" && !Number.isFinite(raw)) {
|
|
4109
|
+
throw new TypeError(`signup-control theme token "${name}" must be a finite string or number`);
|
|
4110
|
+
}
|
|
4111
|
+
const value = String(raw).trim();
|
|
4112
|
+
if (!value || value.length > MAX_VALUE_LENGTH || UNSAFE_VALUE.test(value)) {
|
|
4113
|
+
throw new TypeError(`signup-control theme token "${name}" has an unsafe or oversized value`);
|
|
4114
|
+
}
|
|
4115
|
+
serializedLength += name.length + value.length + 4;
|
|
4116
|
+
if (serializedLength > MAX_SERIALIZED_LENGTH) {
|
|
4117
|
+
throw new TypeError("signup-control theme exceeds the serialized size bound");
|
|
4118
|
+
}
|
|
4119
|
+
tokens[name] = value;
|
|
4120
|
+
}
|
|
4121
|
+
return tokens;
|
|
4122
|
+
}
|
|
4123
|
+
function cssFor(tokens) {
|
|
4124
|
+
const declarations = Object.entries(tokens).map(([name, value]) => ` ${name}: ${value};`);
|
|
4125
|
+
return declarations.length ? `:root {
|
|
4126
|
+
${declarations.join("\n")}
|
|
4127
|
+
}` : "";
|
|
4128
|
+
}
|
|
4129
|
+
function stateFor(revision, options) {
|
|
4130
|
+
if (!revision) {
|
|
4131
|
+
return {
|
|
4132
|
+
css: cssFor(validatedTokens(options.bootstrapTokens)),
|
|
4133
|
+
metadata: { source: "bootstrap", revision: null, digest: null, cacheKey: "signup-control:bootstrap" }
|
|
4134
|
+
};
|
|
4135
|
+
}
|
|
4136
|
+
if (!Number.isSafeInteger(revision.revision) || revision.revision < 1 || !DIGEST.test(revision.digest)) {
|
|
4137
|
+
throw new TypeError("signup-control theme revision metadata is invalid");
|
|
4138
|
+
}
|
|
4139
|
+
return {
|
|
4140
|
+
css: cssFor(validatedTokens(revision.theme?.tokens)),
|
|
4141
|
+
metadata: {
|
|
4142
|
+
source: "active",
|
|
4143
|
+
revision: revision.revision,
|
|
4144
|
+
digest: revision.digest,
|
|
4145
|
+
cacheKey: `signup-control:${revision.digest}`
|
|
4146
|
+
}
|
|
4147
|
+
};
|
|
4148
|
+
}
|
|
4149
|
+
function attributes(metadata) {
|
|
4150
|
+
return [
|
|
4151
|
+
`id="${SIGNUP_CONTROL_THEME_STYLE_ID}"`,
|
|
4152
|
+
`data-signup-control-source="${metadata.source}"`,
|
|
4153
|
+
`data-signup-control-cache-key="${metadata.cacheKey}"`,
|
|
4154
|
+
...metadata.revision === null ? [] : [`data-signup-control-revision="${metadata.revision}"`],
|
|
4155
|
+
...metadata.digest === null ? [] : [`data-signup-control-digest="${metadata.digest}"`]
|
|
4156
|
+
].join(" ");
|
|
4157
|
+
}
|
|
4158
|
+
function renderSignupControlThemeHead(revision, options = {}) {
|
|
4159
|
+
const { css, metadata } = stateFor(revision, options);
|
|
4160
|
+
return { css, metadata, head: `<style ${attributes(metadata)}>${css}</style>` };
|
|
4161
|
+
}
|
|
4162
|
+
function injectSignupControlThemeHead(html, rendered, marker = SIGNUP_CONTROL_THEME_MARKER) {
|
|
4163
|
+
const existing = /<style\b(?=[^>]*\bid\s*=\s*["']odla-signup-control-theme["'])[^>]*>[\s\S]*?<\/style>/gi;
|
|
4164
|
+
const existingBlocks = html.match(existing) ?? [];
|
|
4165
|
+
if (existingBlocks.length > 1) {
|
|
4166
|
+
throw new TypeError("signup-control theme style block must appear at most once");
|
|
4167
|
+
}
|
|
4168
|
+
if (existingBlocks.length === 1) return html.replace(existing, rendered.head);
|
|
4169
|
+
const markerIndex = html.indexOf(marker);
|
|
4170
|
+
if (markerIndex >= 0) {
|
|
4171
|
+
if (html.indexOf(marker, markerIndex + marker.length) >= 0) {
|
|
4172
|
+
throw new TypeError("signup-control theme marker must appear exactly once");
|
|
4173
|
+
}
|
|
4174
|
+
return `${html.slice(0, markerIndex)}${rendered.head}${html.slice(markerIndex + marker.length)}`;
|
|
4175
|
+
}
|
|
4176
|
+
const closingHead = html.search(/<\/head\s*>/i);
|
|
4177
|
+
if (closingHead < 0) throw new TypeError("signup-control theme requires an HTML head");
|
|
4178
|
+
return `${html.slice(0, closingHead)}${rendered.head}${html.slice(closingHead)}`;
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4181
|
+
// src/signup-control-theme-worker.ts
|
|
4182
|
+
async function activeTheme(options) {
|
|
4183
|
+
const active = await loadActiveSignupControl(options.db, options.chapterId, options.environment);
|
|
4184
|
+
if (!active) return null;
|
|
4185
|
+
const checked = validateSignupControlDocument(active.document);
|
|
4186
|
+
if (!checked.ok) throw new TypeError(`active signup-control document is invalid: ${checked.errors.join("; ")}`);
|
|
4187
|
+
const document = checked.value;
|
|
4188
|
+
if (document.revision !== active.revision || document.target.chapterId !== options.chapterId || document.target.environment !== options.environment || active.digest !== await signupControlDigest(document)) {
|
|
4189
|
+
throw new TypeError("active signup-control revision metadata or digest is invalid");
|
|
4190
|
+
}
|
|
4191
|
+
return { revision: active.revision, digest: active.digest, theme: document.theme };
|
|
4192
|
+
}
|
|
4193
|
+
async function renderActiveSignupControlHtml(html, options) {
|
|
4194
|
+
const theme = renderSignupControlThemeHead(await activeTheme(options), {
|
|
4195
|
+
bootstrapTokens: options.bootstrapTokens
|
|
4196
|
+
});
|
|
4197
|
+
return { html: injectSignupControlThemeHead(html, theme, options.marker), theme };
|
|
4198
|
+
}
|
|
4199
|
+
async function contentEtag(html) {
|
|
4200
|
+
const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(html));
|
|
4201
|
+
const hex = Array.from(new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
4202
|
+
return `W/"sha256-${hex}"`;
|
|
4203
|
+
}
|
|
4204
|
+
async function withActiveSignupControlTheme(response, options) {
|
|
4205
|
+
if (!response.ok || !(response.headers.get("content-type") ?? "").toLowerCase().includes("text/html")) {
|
|
4206
|
+
throw new TypeError("signup-control theme can only transform a successful HTML response");
|
|
4207
|
+
}
|
|
4208
|
+
const rendered = await renderActiveSignupControlHtml(await response.text(), options);
|
|
4209
|
+
const headers = new Headers(response.headers);
|
|
4210
|
+
headers.delete("content-length");
|
|
4211
|
+
headers.set("cache-control", "no-store");
|
|
4212
|
+
headers.set("etag", await contentEtag(rendered.html));
|
|
4213
|
+
headers.set("x-odla-signup-control-digest", rendered.theme.metadata.digest ?? "bootstrap");
|
|
4214
|
+
if (rendered.theme.metadata.revision === null) headers.delete("x-odla-signup-control-revision");
|
|
4215
|
+
else headers.set("x-odla-signup-control-revision", String(rendered.theme.metadata.revision));
|
|
4216
|
+
return new Response(rendered.html, { status: response.status, statusText: response.statusText, headers });
|
|
4217
|
+
}
|
|
4218
|
+
|
|
4069
4219
|
// src/worker.ts
|
|
4070
4220
|
var BUILTIN_ROUTES = [
|
|
4071
4221
|
handleHealth,
|